Кэширование в Webpack предназначено для ускорения повторных сборок. После первой компиляции Webpack сохраняет промежуточные результаты: обработанные модули, результаты работы loader’ов, граф зависимостей, оптимизации и другие вычисления. При последующих сборках часть данных может быть переиспользована без повторной обработки.
Однако любая система кэширования требует механизма инвалидации — определения момента, когда сохранённые данные больше нельзя считать актуальными. Если кэш не инвалидируется вовремя, сборка начинает использовать устаревшие результаты. Если инвалидируется слишком часто — исчезает выигрыш в производительности.
Webpack 5 предоставляет несколько механизмов контроля инвалидации кэша:
cache.versioncache.buildDependenciescache.nameЭти параметры особенно важны при использовании файлового кэша
(type: 'filesystem').
При использовании файлового кэша Webpack сохраняет:
Перед новой сборкой Webpack проверяет:
Если хотя бы один фактор отличается — соответствующая часть кэша инвалидируется.
Пример базового файлового кэша:
module.exports = {
cache: {
type: 'filesystem'
}
};
versionversioncache.version — это пользовательский идентификатор
версии кэша. При изменении значения Webpack полностью сбрасывает
существующий кэш и создаёт новый.
Это ручной механизм инвалидации.
Пример:
module.exports = {
cache: {
type: 'filesystem',
version: '1.0'
}
};
Если изменить значение:
version: '2.0'
Webpack перестанет использовать старые данные кэша.
versionWebpack автоматически отслеживает множество изменений, но не все.
Существуют ситуации, когда логика сборки изменилась, а Webpack этого не понимает:
В подобных случаях используется ручная инвалидация через
version.
Допустим, сборка зависит от NODE_ENV.
const env = process.env.NODE_ENV;
module.exports = {
cache: {
type: 'filesystem',
version: env
}
};
Теперь development и production будут использовать разные кэши.
Нередко версия строится автоматически:
const path = require('path');
const packageJson = require('./package.json');
module.exports = {
cache: {
type: 'filesystem',
version: `${packageJson.version}-${process.env.NODE_ENV}`
}
};
Такой подход позволяет:
Предположим, используется собственный loader:
module.exports = function(source) {
return transform(source);
};
Webpack не всегда способен корректно определить изменение внутренней логики loader’а. Особенно если трансформация зависит от внешних файлов.
В этом случае можно обновлять version вручную:
cache: {
type: 'filesystem',
version: 'loader-v3'
}
versionversion: '1'
Подходит для ручного контроля.
version: packageJson.version
Кэш сбрасывается после обновления приложения.
const crypto = require('crypto');
const configHash = crypto
.createHash('md5')
.update(JSON.stringify(customConfig))
.digest('hex');
module.exports = {
cache: {
type: 'filesystem',
version: configHash
}
};
Позволяет автоматически инвалидировать кэш при изменении пользовательской конфигурации.
version: [
process.env.NODE_ENV,
packageJson.version,
customHash
].join('-')
Наиболее распространённый промышленный подход.
buildDependenciesbuildDependenciesbuildDependencies сообщает Webpack, какие файлы влияют
на сборку помимо обычных модулей проекта.
Если один из этих файлов изменяется — кэш инвалидируется.
Пример:
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
}
};
Здесь __filename — текущий webpack.config.js.
buildDependencies необходимWebpack отслеживает зависимости модулей, но не всегда способен определить:
Без buildDependencies Webpack может использовать
устаревший кэш.
buildDependenciesПараметр представляет объект:
buildDependencies: {
имяГруппы: [массивФайлов]
}
Пример:
buildDependencies: {
config: [
__filename,
path.resolve(__dirname, 'build/utils.js')
]
}
Имена групп (config) используются только для логической
организации.
Часто webpack.config.js импортирует внешние файлы:
const aliases = require('./config/aliases');
const rules = require('./config/rules');
Webpack может не всегда корректно отслеживать такие зависимости в сложных сценариях.
Надёжный вариант:
buildDependencies: {
config: [
__filename,
path.resolve(__dirname, 'config/aliases.js'),
path.resolve(__dirname, 'config/rules.js')
]
}
TypeScript-конфигурация влияет на:
Поэтому tsconfig.json желательно добавлять в зависимости
сборки.
buildDependencies: {
config: [
path.resolve(__dirname, 'tsconfig.json')
]
}
buildDependencies: {
babel: [
path.resolve(__dirname, '.babelrc')
]
}
или:
buildDependencies: {
babel: [
path.resolve(__dirname, 'babel.config.js')
]
}
buildDependencies: {
styles: [
path.resolve(__dirname, 'postcss.config.js')
]
}
Webpack поддерживает отслеживание каталогов:
buildDependencies: {
config: [
path.resolve(__dirname, 'config/')
]
}
При изменении любого файла внутри директории кэш инвалидируется.
Однако такой подход может вызывать слишком частые сбросы кэша.
Крупные проекты часто структурируют зависимости:
buildDependencies: {
webpack: [
__filename
],
babel: [
path.resolve(__dirname, 'babel.config.js')
],
typescript: [
path.resolve(__dirname, 'tsconfig.json')
],
styles: [
path.resolve(__dirname, 'postcss.config.js')
]
}
Это улучшает поддержку конфигурации.
Многие plugin’ы и loader’ы используют собственные файлы конфигурации:
Все подобные файлы желательно включать в
buildDependencies.
Webpack автоматически добавляет текущий конфиг в зависимости сборки.
Но если конфигурация разбита на несколько файлов:
webpack/
├── common.js
├── prod.js
├── dev.js
└── loaders.js
необходимо явно указывать дополнительные зависимости.
namenamecache.name задаёт имя кэша.
Webpack использует его для разделения нескольких независимых кэшей.
Пример:
module.exports = {
cache: {
type: 'filesystem',
name: 'client-cache'
}
};
nameБез отдельного имени разные сборки могут конфликтовать:
Разделение кэшей предотвращает:
cache: {
type: 'filesystem',
name: process.env.NODE_ENV
}
Теперь создаются разные кэши:
В SSR-проектах:
cache: {
type: 'filesystem',
name: 'client'
}
и отдельно:
cache: {
type: 'filesystem',
name: 'server'
}
Webpack поддерживает массив конфигураций:
module.exports = [
clientConfig,
serverConfig
];
Каждая конфигурация должна иметь собственный name.
Пример:
const clientConfig = {
name: 'client',
cache: {
type: 'filesystem',
name: 'client-cache'
}
};
const serverConfig = {
name: 'server',
cache: {
type: 'filesystem',
name: 'server-cache'
}
};
name и
cacheDirectoryWebpack формирует путь к кэшу на основе:
cacheDirectorynameПример:
cache: {
type: 'filesystem',
cacheDirectory: path.resolve(__dirname, '.webpack-cache'),
name: 'frontend'
}
Фактический путь будет примерно таким:
.webpack-cache/frontend
Иногда имя зависит от платформы:
name: `cache-${process.platform}`
или:
name: `cache-${process.env.NODE_ENV}`
В monorepo:
packages/
├── admin/
├── storefront/
└── api/
каждый пакет может иметь собственный кэш:
cache: {
type: 'filesystem',
name: 'admin'
}
version, buildDependencies и
nameНа практике эти параметры работают совместно.
Типичная промышленная конфигурация:
const path = require('path');
const packageJson = require('./package.json');
module.exports = {
cache: {
type: 'filesystem',
name: process.env.NODE_ENV,
version: packageJson.version,
buildDependencies: {
config: [
__filename,
path.resolve(__dirname, 'tsconfig.json'),
path.resolve(__dirname, 'babel.config.js'),
path.resolve(__dirname, 'postcss.config.js')
]
}
}
};
buildDependenciesЧастая проблема:
cache: {
type: 'filesystem'
}
При этом проект зависит от:
В результате Webpack использует устаревший кэш.
name
для разных сборокПлохой вариант:
cache: {
type: 'filesystem',
name: 'default'
}
для всех конфигураций одновременно.
Это может приводить к конфликтам кэша.
versionОшибка:
version: Date.now().toString()
Кэш будет инвалидироваться на каждой сборке.
Фактически кэширование перестаёт работать.
buildDependenciesПлохой пример:
buildDependencies: {
config: [
__dirname
]
}
Любое изменение в проекте будет уничтожать кэш.
Если сборка зависит от:
NODE_ENV;но они не участвуют в version или name,
возможны ошибки повторного использования кэша.
Иногда требуется ручной сброс:
rm -rf node_modules/.cache/webpack
Windows:
Remove-Item node_modules/.cache/webpack -Recurse -Force
Полезен инфраструктурный логгер:
infrastructureLogging: {
level: 'verbose'
}
Webpack начинает выводить:
Если кэш постоянно пересоздаётся:
version;name;buildDependencies;Типичная архитектура:
const path = require('path');
const packageJson = require('./package.json');
module.exports = {
cache: {
type: 'filesystem',
cacheDirectory: path.resolve(
__dirname,
'node_modules/.cache/webpack'
),
name: [
process.env.NODE_ENV,
process.platform
].join('-'),
version: [
packageJson.version,
process.env.BUILD_VERSION
].join('-'),
buildDependencies: {
webpack: [
__filename
],
babel: [
path.resolve(__dirname, 'babel.config.js')
],
typescript: [
path.resolve(__dirname, 'tsconfig.json')
],
styles: [
path.resolve(__dirname, 'postcss.config.js')
]
}
}
};
Такая конфигурация обеспечивает: