Сборка frontend-проектов в CI/CD почти всегда ограничивается не вычислительной мощностью сервера, а количеством повторно выполняемых операций: загрузкой зависимостей, транспиляцией, минификацией, повторным анализом модулей и генерацией артефактов. Webpack активно использует кэширование для сокращения этих затрат, однако в среде CI/CD стратегия работы с кэшем отличается от локальной разработки.
В локальной среде кэш живёт долго и постепенно прогревается. В CI/CD окружение часто является эфемерным: контейнеры пересоздаются, файловая система очищается, а пайплайны запускаются параллельно. Неправильно организованный кэш либо не приносит пользы, либо начинает вызывать нестабильность сборок.
Основная задача — определить:
В контексте Webpack и CI/CD обычно используются несколько независимых уровней кэширования.
Наиболее важный и безопасный уровень:
Этот кэш позволяет не скачивать зависимости повторно.
Webpack 5 поддерживает persistent cache:
module.exports = {
cache: {
type: 'filesystem'
}
};
Содержит:
Если используется babel-loader, может применяться
отдельный кэш:
{
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
TypeScript incremental build:
{
"compilerOptions": {
"incremental": true
}
}
Создаёт .tsbuildinfo.
ESLint:
eslint --cache
Stylelint:
stylelint --cache
Если сборка выполняется внутри Docker, может кэшироваться:
Это наиболее эффективный тип кэша в CI/CD.
cache:
paths:
- ~/.npm
cache:
paths:
- .yarn/cache
cache:
paths:
- ~/.pnpm-store
Скачивание тысяч пакетов из registry значительно медленнее чтения локального кэша.
Иногда кэшируют полностью каталог node_modules.
Преимущества:
Недостатки:
Кэширование node_modules особенно полезно:
Нежелательно использовать:
Webpack persistent cache даёт очень серьёзный прирост скорости.
Пример:
module.exports = {
cache: {
type: 'filesystem',
cacheDirectory: path.resolve(__dirname, '.webpack-cache')
}
};
В CI/CD это может сокращать время:
Babel является одним из самых дорогих этапов сборки.
{
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false
}
}
В CI/CD кэш Babel особенно полезен при:
При использовании ts-loader или отдельного
tsc incremental cache существенно ускоряет проверки
типов.
{
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo"
}
Частая ошибка — кэширование каталога сборки:
cache:
paths:
- dist/
Проблемы:
dist должен считаться одноразовым артефактом.
Source maps могут занимать гигабайты.
Их кэширование редко оправдано:
Кэширование:
.min.js;.min.css;обычно бессмысленно.
Причины:
Нельзя кэшировать:
Кэш должен инвалидироваться автоматически при изменении:
Наиболее распространённая стратегия:
key:
files:
- package-lock.json
или:
key:
files:
- yarn.lock
Изменение зависимостей автоматически сбрасывает кэш.
Очень важно включать Node.js version в cache key.
Пример:
key: node-18-yarn-${{ hashFiles('yarn.lock') }}
Иначе возможны:
Webpack filesystem cache умеет автоматически отслеживать:
Пример:
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
}
Плохая практика:
key: webpack-cache
Хорошая практика:
key: webpack-${CI_COMMIT_REF_SLUG}-${NODE_VERSION}
Иначе возникают:
Для feature branches желательно использовать отдельный cache namespace.
Причины:
Правильный Dockerfile:
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
Тогда dependencies layer кэшируется отдельно от source code.
COPY . .
RUN yarn install
Любое изменение файла полностью инвалидирует install layer.
Современный Docker BuildKit поддерживает mount cache:
RUN --mount=type=cache,target=/root/.npm npm install
Это значительно ускоряет CI.
Подходит для:
Недостаток — кэш не переносится между агентами.
Используются:
Преимущества:
Недостатки:
В monorepo влияние кэша особенно велико.
Без кэша:
Кэш должен быть сегментирован:
Пример:
key: webapp-${HASH}
а не:
key: monorepo-global
Наиболее опасная проблема.
Проявления:
Если кэш повреждён:
При shared cache возможна ситуация, когда:
Почти всегда стоит кэшировать:
cache:
paths:
- ~/.npm
- .webpack-cache
Дополнительно:
cache:
paths:
- ~/.npm
- .webpack-cache
- .eslintcache
- .tsbuildinfo
Обычно используются:
- uses: actions/cache@v4
with:
path: |
~/.npm
.webpack-cache
key: |
node-${{ matrix.node }}-${{ hashFiles('package-lock.json') }}
cache:
key:
files:
- package-lock.json
paths:
- node_modules/
- .webpack-cache/
Слишком маленький кэш:
Слишком большой кэш:
Наиболее эффективный CI cache: