Миграция Tailwind v3 → v4 в большом монорепозитории
На этой странице
@import, @config, @source, @reference), что делать с Sass и как закрепить результат проверкой в CI.Что поменялось в v4, если по-простому
Tailwind — это генератор CSS. Он читает твои файлы, находит в них классы (flex, bg-primary, px-4) и пишет CSS только для тех, что реально используются.
В v3 это настраивалось в tailwind.config.js: цвета, шрифты и массив content — где искать классы.
В v4:
- Настройка переехала в CSS. Цвета и шрифты задаются через
@themeпрямо в CSS-файле. - Поиск классов автоматический. Массива
contentбольше нет: Tailwind сам сканирует проект, начиная от папки, где лежит CSS. - Свой PostCSS-плагин. Вместо
tailwindcssв PostCSS — пакет@tailwindcss/postcss. - Autoprefixer больше не нужен — префиксы v4 расставляет сам.
- Планка браузеров выше: примерно Chrome 111, Safari 16.4, Firefox 128. Если у тебя пользователи на старых Safari — проверь это до начала, а не после.
Шаг 1. PostCSS
Было:
// postcss.config.js
module.exports = {
plugins: { tailwindcss: {}, autoprefixer: {} },
};Стало:
// .postcssrc.json
{ "plugins": { "@tailwindcss/postcss": {} } }autoprefixer удаляем из package.json.
Шаг 2. Директивы в главном CSS
Директив @tailwind base; @tailwind components; @tailwind utilities; в v4 нет. Вместо них — обычные импорты по слоям:
@layer theme, base, components, utilities;
@import "tailwindcss/theme" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities" layer(utilities);Можно и одной строкой @import "tailwindcss";. Мы расписали слои явно, потому что поверх Tailwind лежат стили Angular Material, и порядок слоёв решает, кто кого перебивает.
Шаг 3. Старый JS-конфиг можно оставить
Переписывать всю тему в @theme за один раз не обязательно. Директива @config подключает старый JS-конфиг или пресет:
@config "../tailwind.config.js";У нас общий пресет с токенами цветов и шкалой шрифтов, на который ссылаются все приложения. Переписать его в CSS — отдельная задача. Миграцию лучше делать маленькими шагами: сначала «собирается и выглядит так же», потом — улучшения.
Тёмный режим через атрибут, который в v3 задавался в конфиге (darkMode: '[data-mode="dark"]'), в v4 объявляется в CSS:
@custom-variant dark ([data-mode="dark"] &);Шаг 4. @source: главная ловушка монорепозитория
Автопоиск классов начинает с папки, где лежит CSS-файл, и не выходит за её пределы так, как тебе нужно. В монорепозитории общий CSS лежит в библиотеке UI-кита, а классы используются ещё в десятке библиотек и в приложениях.
Результат: сборка проходит, ошибок нет, а у половины страниц нет отступов. Tailwind просто не увидел файлы, где эти классы написаны, и не сгенерировал для них CSS.
Решение — явно перечислить, где искать:
@source "./lib/**/*.{ts,html}";
@source "../../shared/**/*.{ts,html}";
@source "../../apps/my-app/src/**/!(*.stories|*.spec).{ts,html}";Правило на будущее: новое приложение или библиотека, которые используют этот CSS, — новая строка @source. Об этом стоит написать комментарий прямо в файле, иначе следующий человек снова получит «пропавшие стили».
Шаг 5. @reference в стилях компонентов
В Angular стили каждого компонента компилируются отдельно. Если в .scss компонента есть @apply:
.card { @apply rounded-xl p-4 bg-background-1; }то в v4 этот файл ничего не знает о теме и твоих токенах — их определения лежат в главном CSS. Сборка падает с «Cannot apply unknown utility class».
Лечение — первой строкой в файле сослаться на главный CSS:
@reference "../../shared-styles.css";@reference подтягивает определения, но не дублирует сам CSS в каждый компонент. Обычный @import здесь был бы ошибкой: весь Tailwind оказался бы в стилях каждого компонента.
Шаг 6. Sass и Tailwind v4 не дружат
Tailwind v4 рассчитан на то, что он сам и есть препроцессор. Связка «Sass, а внутри директивы Tailwind» работает плохо: Sass пытается разобрать @source, @reference, @custom-variant раньше Tailwind.
Что сделали:
- главный файл стилей с директивами Tailwind — обычный
.css; - то, что действительно требует Sass (тема Angular Material на Sass-миксинах), вынесли в отдельный
.scssи подключили рядом вproject.json.
Шаг 7. Переименованные классы
В v4 часть классов сдвинулась на ступень, например shadow-sm → shadow-xs, rounded-sm → rounded-xs, outline-none → outline-hidden, а у ring по умолчанию ширина 1px вместо 3px. Официальная утилита npx @tailwindcss/upgrade переименовывает большую часть автоматически.
Но если тени и радиусы у тебя свои, из пресета, — их имена не трогай. Проверь глазами, а не доверяй переименованию вслепую.
Ошибка, которую я сделал
Перед правкой SCSS-файлов я сохранял копии *.scss.bak на всякий случай. Они уехали в коммит. Мелочь, но:
- бэкапы — это то, для чего есть git; отдельные копии не нужны;
- перед коммитом —
git statusи просмотр списка файлов, а.bak— в.gitignore.
Как не пустить старое обратно
Миграция заканчивается не в момент, когда всё собралось, а когда старые привычки не могут вернуться. После перехода мы добавили в проверку MR маленький скрипт. Он смотрит только добавленные строки в .css/.scss и валит проверку, если:
- цвет задан вручную (
#fff,rgb(...)) вместо токена; - использован CSS новее планки браузеров (
@starting-style, anchor positioning,field-sizingи т. п.) без@supports.
Учебная версия такой проверки — десяток строк:
// check-styles.mjs (учебный пример)
import { execFileSync } from 'node:child_process';
const diff = execFileSync('git', ['diff', '-U0', 'origin/main', '--', '*.css', '*.scss'], { encoding: 'utf8' });
const added = diff.split('\n').filter(l => l.startsWith('+') && !l.startsWith('+++'));
const bad = added.filter(l => /#[0-9a-f]{3,8}\b|\brgba?\((?!\s*var\()/i.test(l));
if (bad.length) {
console.error('Цвет задан вручную, используй токен:\n' + bad.join('\n'));
process.exit(1);
}Почему только добавленные строки: старых нарушений может быть сотни, и требовать исправить их все в каждом MR — значит, проверку быстро отключат. Новое — нельзя, старое — разбираем постепенно.
Чек-лист
- Проверить планку браузеров пользователей.
- PostCSS →
@tailwindcss/postcss, убрать autoprefixer. @tailwind ...→@importслоёв.- Старый конфиг — через
@config, переписывать потом. @sourceна каждую библиотеку и приложение.@referenceв каждом файле стилей компонента с@apply.- Директивы Tailwind — в
.css, Sass — отдельно. - Пройтись по экранам глазами: десктоп и 375px.
- Проверка в CI, чтобы старое не вернулось.
Связанные посты: nx-monorepo-from-copies, ci-nx-affected.