Память как тип кэша: memory cache

memory cache — встроенный механизм кэширования Webpack, при котором результаты сборки сохраняются в оперативной памяти процесса Node.js. Такой кэш существует только во время жизни текущего процесса и полностью очищается после завершения работы сборщика.

В Webpack 5 память стала полноценным уровнем кэширования, позволяющим значительно ускорять повторные компиляции, rebuild-процессы и работу dev server.

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

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

Принцип работы memory cache

Во время компиляции Webpack выполняет множество дорогостоящих операций:

  • чтение файлов;
  • парсинг JavaScript;
  • построение dependency graph;
  • выполнение loader-цепочек;
  • трансформацию AST;
  • генерацию chunk’ов;
  • минификацию;
  • вычисление hash;
  • анализ зависимостей.

Memory cache позволяет не выполнять повторно операции, результаты которых уже известны.

Например:

src/index.js
 ├── import './app.js'
 ├── import './styles.css'
 └── import 'lodash'

После первой компиляции Webpack сохраняет в памяти:

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

При повторной сборке неизменённые модули извлекаются из памяти мгновенно.

Разница между memory cache и filesystem cache

Memory cache

Хранение:

RAM процесса Node.js

Жизненный цикл:

До завершения процесса

Скорость:

Очень высокая

Персистентность:

Нет

Filesystem cache

Хранение:

Файлы на диске

Жизненный цикл:

Между запусками Webpack

Скорость:

Ниже memory cache

Персистентность:

Да

Пример filesystem cache:

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

Когда memory cache особенно эффективен

webpack-dev-server

Наиболее распространённый сценарий.

Во время разработки процесс Webpack живёт долго:

npm run dev

или:

webpack serve

Изменения происходят постоянно, а memory cache ускоряет incremental rebuild.

Пример:

Первая сборка: 12 секунд
Повторная сборка: 300 мс

Watch mode

webpack --watch

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

Memory cache хранит:

  • старые dependency graph;
  • loader results;
  • chunk metadata;
  • parsed modules.

Hot Module Replacement

При HMR происходит частичная замена модулей без полной перезагрузки приложения.

Memory cache здесь критически важен, потому что:

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

Поведение cache.type = ‘memory’

Настройка:

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

означает:

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

Поведение по умолчанию

Webpack 5 автоматически использует memory cache в development mode.

Эквивалентно:

module.exports = {
    mode: 'development',
    
    cache: {
        type: 'memory'
    }
};

В production режиме кэш часто отключён или используется filesystem cache.

Архитектура memory cache

Внутри Webpack кэшируются разные сущности.

Cached modules

Webpack хранит:

  • source code;
  • parsed AST;
  • exports/imports metadata;
  • dependency info.

Cached loader results

Пример:

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

babel-loader — дорогая операция.

Memory cache сохраняет результат трансформации:

ESNext → ES5

Повторная обработка не требуется.

Cached resolver results

Webpack активно использует resolver:

import Button from '@/components/Button';

Разрешение путей:

  • поиск файлов;
  • alias;
  • extensions;
  • package exports;
  • symlink resolution.

Результаты также кэшируются.

Cached chunks

Chunk graph строится дорого.

Memory cache хранит:

  • chunk relations;
  • runtime dependencies;
  • splitChunks metadata;
  • hash data.

Cache invalidation

Ключевая задача любого кэша — корректная инвалидизация.

Webpack отслеживает:

  • изменение файлов;
  • timestamp;
  • hash;
  • dependency changes;
  • loader changes;
  • config changes.

Если модуль изменился:

src/utils.js

Webpack пересобирает:

  • сам модуль;
  • зависимые модули;
  • affected chunks.

Остальная часть кэша остаётся валидной.

Incremental compilation

Memory cache тесно связан с incremental compilation.

Пример:

1000 модулей

Изменился один файл:

src/Button.js

Без кэша:

1000 модулей пересобираются

С memory cache:

Пересобирается только dependency subtree

Это фундамент высокой скорости Webpack Dev Server.

Влияние на потребление памяти

Memory cache ускоряет сборку ценой увеличения RAM usage.

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

1–4 GB RAM

Иногда больше.

Особенно при использовании:

  • Babel;
  • TypeScript;
  • source maps;
  • large monorepo;
  • thousands of modules.

Очистка memory cache

Memory cache очищается автоматически:

Процесс завершён → кэш уничтожен

Например:

CTRL + C

или:

kill node

Также кэш пересоздаётся после:

  • crash;
  • restart dev server;
  • restart CI job.

Настройка cache

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

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

Полное отключение кэша

module.exports = {
    cache: false
};

Такой режим полезен:

  • при отладке loader’ов;
  • при диагностике cache bugs;
  • при profiling;
  • при поиске race conditions.

Cache и loaders

Многие loader’ы имеют собственный уровень кэширования.

Пример:

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

Здесь возникает два уровня кэша:

Babel cache

Трансформированный JS

Webpack memory cache

Compilation results

Совместное использование даёт максимальное ускорение.

Cache и thread-loader

Пример:

{
    test: /\.js$/,
    use: [
        'thread-loader',
        'babel-loader'
    ]
}

thread-loader создаёт worker pool.

Memory cache уменьшает необходимость повторной работы потоков.

Без кэша:

Потоки постоянно заново компилируют модули

С кэшем:

Многие результаты извлекаются мгновенно

Cache и source maps

Source maps значительно увеличивают объём кэшируемых данных.

Особенно:

devtool: 'source-map'

или:

devtool: 'inline-source-map'

Поскольку Webpack хранит:

  • transformed source;
  • mappings;
  • original source;
  • generated code.

Это может резко увеличить потребление памяти.

Memory pressure

При недостатке RAM возможны проблемы:

  • garbage collection pauses;
  • slowdown;
  • heap overflow;
  • crash процесса.

Типичная ошибка:

JavaScript heap out of memory

Увеличение heap size

Node.js позволяет увеличить память:

node --max-old-space-size=4096 node_modules/webpack/bin/webpack.js

4096:

4 GB RAM

Для крупных monorepo это часто обязательно.

Влияние garbage collector

Memory cache напрямую зависит от GC V8.

При больших объёмах кэша:

  • GC начинает работать чаще;
  • появляются stop-the-world pauses;
  • rebuild latency растёт.

Иногда слишком большой memory cache ухудшает производительность.

Типичные сценарии деградации

Огромные source maps

devtool: 'inline-source-map'

Thousands of modules

10k+ modules

Heavy Babel transforms

preset-env
polyfills
typescript
decorators

Large CSS pipelines

sass-loader
postcss-loader
css-loader

Memory cache и persistent cache

Webpack 5 позволяет комбинировать memory cache с filesystem cache.

Filesystem cache хранит данные между перезапусками, а memory cache ускоряет текущий runtime.

Внутри одного dev server возможна схема:

Disk cache → RAM cache → Compilation

Snapshot system

Webpack 5 использует snapshot system для определения валидности кэша.

Snapshot хранит:

  • timestamps;
  • hashes;
  • file metadata;
  • directory metadata;
  • managed paths.

Если snapshot совпадает:

Кэш считается валидным

Build dependencies

Webpack отслеживает зависимости сборки.

Например:

module.exports = {
    cache: {
        buildDependencies: {
            config: [__filename]
        }
    }
};

Если изменится webpack.config.js:

Кэш инвалидируется

Managed paths

Webpack считает некоторые директории стабильными.

Например:

node_modules

Это уменьшает количество проверок.

Настройка:

module.exports = {
    snapshot: {
        managedPaths: [
            /node_modules/
        ]
    }
};

Immutable paths

Некоторые пути можно считать полностью неизменяемыми.

Пример:

module.exports = {
    snapshot: {
        immutablePaths: [
            path.resolve(__dirname, 'vendor')
        ]
    }
};

Webpack перестаёт проверять их содержимое.

Cache и development mode

Development mode максимально ориентирован на скорость rebuild.

Webpack:

  • снижает уровень оптимизаций;
  • активно использует memory cache;
  • избегает тяжёлых вычислений;
  • ускоряет rebuild pipeline.

Cache и production mode

Production build обычно запускается:

Один раз

Поэтому memory cache менее полезен.

Пример:

webpack --mode production

После завершения процесса кэш исчезнет.

Для production чаще используется filesystem cache.

Cache и CI/CD

В CI memory cache почти бесполезен:

Процесс стартует → build → процесс завершается

RAM cache не успевает принести выгоду.

Лучше использовать:

cache: {
    type: 'filesystem'
}

Profiling memory cache

Для анализа производительности используются:

webpack --profile

и:

node --inspect

Также полезны:

  • Chrome DevTools;
  • heap snapshots;
  • flame charts;
  • V8 profiling.

Признаки эффективного memory cache

Быстрые rebuild

< 1 секунды

Стабильное RAM usage

Без постоянного роста памяти.

Минимальный invalidation scope

Изменение одного файла не вызывает full rebuild.

Признаки проблем

Постоянный рост памяти

memory leak

Долгий rebuild

Несмотря на cache.

Частые full recompilation

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

GC pauses

[GC] Mark-sweep ...

в консоли Node.js.

Практическая конфигурация для разработки

const path = require('path');

module.exports = {
    mode: 'development',

    cache: {
        type: 'memory'
    },

    devtool: 'eval-cheap-module-source-map',

    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        cacheDirectory: true
                    }
                }
            }
        ]
    },

    resolve: {
        extensions: ['.js']
    }
};

Особенности:

  • быстрый rebuild;
  • минимальная нагрузка на disk IO;
  • высокая скорость HMR;
  • уменьшение повторной работы Babel.

Практическая конфигурация для больших проектов

module.exports = {
    cache: {
        type: 'memory'
    },

    parallelism: 50,

    snapshot: {
        managedPaths: [
            /node_modules/
        ]
    },

    infrastructureLogging: {
        level: 'warn'
    }
};

Сравнение rebuild pipeline

Без memory cache

File change
    ↓
Read file
    ↓
Parse
    ↓
Loader execution
    ↓
AST transform
    ↓
Dependency analysis
    ↓
Chunk rebuild
    ↓
Emit

С memory cache

File change
    ↓
Invalidate affected module
    ↓
Reuse cached graph
    ↓
Reuse cached loaders
    ↓
Partial rebuild
    ↓
Emit

Разница во времени может составлять десятки раз.

Внутренние механизмы Webpack cache

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

  • CacheFacade;
  • MemoryCachePlugin;
  • compilation cache API;
  • module graph cache;
  • resolver cache.

Многие внутренние сущности сериализуются и переиспользуются между этапами compilation pipeline.

Ограничения memory cache

Не переживает restart

npm run dev
↓
stop
↓
start again
↓
cache empty

Ограничен объёмом RAM

Очень крупные проекты могут упираться в память.

Может маскировать проблемы

Иногда баг проявляется только без кэша.

Не подходит для CI

Из-за короткого жизненного цикла процессов.

Когда memory cache — лучший выбор

Локальная разработка

Особенно:

  • React;
  • Vue;
  • Angular;
  • large SPA.

HMR

Где rebuild должен быть почти мгновенным.

Active watch mode

При постоянных изменениях файлов.

Monorepo development

Если процесс Webpack работает длительное время.

Когда лучше filesystem cache

Долгие production builds

CI pipelines

Частые restart процесса

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

Filesystem cache снижает давление на память.

Комбинирование с другими оптимизациями

Memory cache особенно эффективен вместе с:

  • thread-loader;
  • babel-loader cacheDirectory;
  • splitChunks;
  • lazy compilation;
  • managedPaths;
  • persistent filesystem cache;
  • eval-cheap-module-source-map.

Совокупный эффект может уменьшить rebuild:

с 15–20 секунд
до 200–500 мс