Перейти к содержимому

15. Кэширование и хэши

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

Без правильного кэширования каждое обновление приложения заставляет пользователей перезагружать все файлы, включая неизменённые (React, lodash и т.д.):

Без кэширования:
bundle.js → Загрузка 580 KB при каждом обновлении
С правильным кэшированием:
main.a1b2c3.js (собственный код) → Загрузка 85 KB при обновлении
vendor.d4e5f6.js (React + libs) → Из кеша (не изменился)
output: {
filename: '[name].[contenthash:8].js',
// contenthash меняется только если содержимое файла изменилось
// Не меняется если изменился другой файл!
}

Разница между хешами:

Тип хешаИзменяется приИспользование
[hash]Любом изменении в сборкеУстарел, не рекомендуется
[chunkhash]Изменении конкретного чанкаJS бандлы (устаревает)
[contenthash]Изменении содержимого файлаРекомендуется для всего
optimization: {
runtimeChunk: 'single',
// Webpack runtime (маппинг чанков) в отдельный файл
// Без этого: изменение одного файла → инвалидируются хеши всех бандлов!
}

Без runtimeChunk:

main.abc123.js ← содержит runtime + код
vendors.def456.js
# При изменении App.tsx:
main.xyz789.js ← хеш изменился ✓
vendors.mno012.js ← хеш тоже изменился! ✗ (runtime обновился)

С runtimeChunk: 'single':

runtime.qqq111.js ← только runtime (маленький, часто меняется)
main.abc123.js ← только код (меняется при изменении App.tsx)
vendors.def456.js ← библиотеки (почти никогда не меняются) ✓
optimization: {
splitChunks: {
cacheGroups: {
// React и его друзья — меняются редко (при обновлении версий)
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router)[\\/]/,
name: 'react-vendor',
chunks: 'all',
priority: 30,
},
// Все остальные node_modules
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 20,
},
// Общий код приложения (утилиты, hooks)
common: {
name: 'common',
minChunks: 2,
priority: 10,
reuseExistingChunk: true,
},
},
},
}
cache: {
type: 'filesystem', // Кешировать на диск между сборками
cacheDirectory: path.resolve(__dirname, '.webpack-cache'),
// Инвалидировать кеш при изменении конфига
buildDependencies: {
config: [__filename],
},
// Версия кеша (менять при breaking changes)
version: '1.0.0',
}
// Результат:
// Первая сборка: 45s
// Повторная сборка: 2-3s! 🚀
# Nginx — длинный кеш для файлов с хешем в имени
location ~* \.[0-9a-f]{8}\.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Короткий кеш для index.html (не имеет хеша)
location = /index.html {
expires 0;
add_header Cache-Control "no-cache";
}
module.exports = {
mode: 'production',
output: {
filename: '[name].[contenthash:8].js',
chunkFilename: '[id].[contenthash:8].js',
assetModuleFilename: 'assets/[hash:8][ext]',
clean: true,
},
optimization: {
runtimeChunk: 'single',
moduleIds: 'deterministic', // Стабильные ID модулей
chunkIds: 'deterministic',
splitChunks: {
chunks: 'all',
cacheGroups: {
react: { test: /react/, name: 'react-vendor', priority: 30, chunks: 'all' },
vendors: { test: /node_modules/, name: 'vendors', priority: 20, chunks: 'all' },
common: { name: 'common', minChunks: 2, priority: 10 },
},
},
},
cache: {
type: 'filesystem',
buildDependencies: { config: [__filename] },
},
};

Ниже — интерактивный симулятор кэширования. Изменяйте файлы и смотрите как меняются хеши бандлов.