Извлечение
UnoCSS работает путём поиска использований утилит в вашей кодовой базе и генерации соответствующего CSS по запросу. Мы называем этот процесс извлечением.
Источники контента
UnoCSS поддерживает извлечение использований утилит из нескольких источников:
- Конвейер — Извлечение прямо из конвейера инструментов сборки.
- Файловая система — Извлечение из файловой системы путём чтения и отслеживания файлов.
- Инлайн — Извлечение из обычного встроенного текста.
Использование утилит из разных источников будет объединено для генерации итогового CSS.
Извлечение из конвейера инструментов сборки
Это поддерживается в интеграциях Vite и Webpack.
UnoCSS будет считывать контент, проходящий через конвейер ваших инструментов сборки, и извлекать из него использование утилит. Это наиболее эффективный и точный способ извлечения, так как мы интеллектуально извлекаем только те утилиты, которые реально используются в вашем приложении, без дополнительных операций ввода-вывода файлов в процессе.
По умолчанию UnoCSS извлекает использования утилит из файлов в вашем конвейере сборки с расширениями .jsx, .tsx, .vue, .md, .html, .svelte, .astro, .marko, а затем генерирует соответствующий CSS по запросу. Файлы .js и .ts НЕ включены по умолчанию.
Чтобы настроить это поведение, вы можете обновить свой uno.config.ts:
export default defineConfig({
content: {
pipeline: {
include: [
// по умолчанию
/\.(vue|svelte|[jt]sx|vine.ts|mdx?|astro|elm|php|phtml|marko|html)($|\?)/,
// включаем файлы js/ts
'src/**/*.{js,ts}',
],
// исключаем файлы
// exclude: []
},
},
})Вы также можете добавить «магический комментарий» @unocss-include в любое место файла, который должен быть просканирован UnoCSS (это делается для каждого файла отдельно). Если вам нужно сканировать файлы *.js или *.ts, добавьте их в конфигурацию, чтобы включить все JS/TS-файлы в качестве целей сканирования.
// ./some-utils.js
// так как файлы .js не включены по умолчанию,
// следующий комментарий заставляет UnoCSS принудительно просканировать этот файл.
// @unocss-include
export const classes = {
active: 'bg-primary text-white',
inactive: 'bg-gray-200 text-gray-500',
}Аналогично, вы можете добавить @unocss-ignore, чтобы пропустить сканирование и трансформацию всего файла.
Если вы хотите, чтобы UnoCSS пропустил блок кода и не выполнял извлечение внутри него, вы можете использовать @unocss-skip-start и @unocss-skip-end. Обратите внимание, что для корректной работы их необходимо использовать в паре.
<p class="text-green text-xl">Зелёный большой</p>
<!-- @unocss-skip-start -->
<!-- `text-red` не будет извлечён -->
<p class="text-red">Red</p>
<!-- @unocss-skip-end -->Извлечение из файловой системы
В случаях, когда вы используете интеграции, не имеющие доступа к конвейеру инструментов сборки (например, плагин PostCSS), или выполняете интеграцию с бэкенд-фреймворками, где код не проходит через этот конвейер, вы можете вручную указать файлы для извлечения.
export default defineConfig({
content: {
filesystem: [
'src/**/*.php',
'public/*.html',
],
},
})Соответствующие файлы будут считываться напрямую из файловой системы и отслеживаться на предмет изменений в режиме разработки.
Извлечение из инлайн-текста
Кроме того, вы также можете извлекать использование утилит из встроенного текста, который вы можете получать откуда-либо ещё.
Вы также можете передать асинхронную функцию для возврата контента. Однако обратите внимание, что эта функция будет вызвана только один раз во время сборки.
export default defineConfig({
content: {
inline: [
// обычный текст
'<div class="p-4 text-red">Какой-то текст</div>',
// асинхронный геттер
async () => {
const response = await fetch('https://example.com')
return response.text()
},
],
},
})Ограничения
Поскольку UnoCSS работает во время сборки, это означает, что будут сгенерированы и отправлены в ваше приложение только статически представленные утилиты. Утилиты, которые используются динамически или подгружаются из внешних ресурсов во время выполнения (runtime), могут быть НЕ обнаружены или не применены.
Белый список
Иногда вам может понадобиться использовать динамическую конкатенацию, например:
<div class="p-${size}"></div>
<!-- это не сработает! -->Поскольку UnoCSS работает во время сборки, используя статическое извлечение, на этапе компиляции он не может знать абсолютно все комбинации утилит. Для таких случаев вы можете настроить опцию safelist.
safelist: 'p-1 p-2 p-3 p-4'.split(' ')Соответствующий CSS будет генерироваться всегда:
.p-1 { padding: 0.25rem; }
.p-2 { padding: 0.5rem; }
.p-3 { padding: 0.75rem; }
.p-4 { padding: 1rem; }Или более гибкий вариант:
safelist: [
...Array.from({ length: 4 }, (_, i) => `p-${i + 1}`),
]Если вы ищете настоящую динамическую генерацию во время выполнения, обратите внимание на пакет @unocss/runtime.
Статический список комбинаций
Ещё один способ обойти ограничение с динамически создаваемыми утилитами — использовать объект, в котором перечислены все комбинации статически. Например, если вы хотите сделать так:
<div class="text-${color} border-${color}"></div>
<!-- это не сработает! -->Вы можете создать объект, в котором перечислены все комбинации (при условии, что вы знаете все возможные значения color, которые собираетесь использовать)
// Поскольку они статичны, UnoCSS сможет извлечь их во время сборки
const classes = {
red: 'text-red border-red',
green: 'text-green border-green',
blue: 'text-blue border-blue',
}А затем используйте в своем шаблоне:
<div class="${classes[color]}"></div>Чёрный список
По аналогии с safelist, вы также можете настроить blocklist, чтобы исключить генерацию некоторых утилит. Это полезно для исключения ложных срабатываний при извлечении.
Но чёрный список — это гораздо более мощный инструмент, чем просто список исключений. В крупных проектах с большим количеством участников гибкость UnoCSS — когда один и тот же визуальный результат может быть достигнут с помощью разных синтаксисов утилит — становится недостатком. Разные разработчики могут писать border, border-1 или b для одного и того же CSS-результата, что приводит к дублированию правил в сгенерированной таблице стилей, несогласованности кода во время ревью и увеличению размера CSS-бандла. Чёрный список позволяет принудительно установить единый канонический синтаксис во всей кодовой базе, блокируя многословные или нестандартные альтернативы и указывая разработчикам на предпочтительную форму записи. В сочетании с @unocss/eslint-plugin, он действует как автоматизированное руководство по стилю, которое ограничивает утилиты токенами дизайн-системы, требует использования кратчайших псевдонимов и предотвращает использование произвольных значений — сохраняя минимальный объем CSS и единообразие кодовой базы при масштабировании.
Типы сопоставления
Опция blocklist принимает три типа сопоставления:
Строка — точное совпадение:
blocklist: [
'p-1', // блокирует p-1 полностью
'tab', // блокирует tab полностью
]Регулярное выражение — сопоставление с шаблоном (использует .test()):
blocklist: [
/^p-[2-4]$/, // блокирует p-2, p-3, p-4
/^border$/, // блокирует "border", но не "border-2"
]Функция — пользовательская логика, возвращает true, чтобы заблокировать:
blocklist: [
s => s.endsWith('px'), // блокировать все классы с суффиксом px
s => s.split('-').length > 4, // блокировать слишком длинные утилиты
]Сообщения
Каждое сопоставление можно опционально обернуть в кортеж, содержащий сообщение message, объясняющее, почему утилита заблокирована. Сообщение может быть статической строкой или callback-функцией, которая получает совпавший селектор:
blocklist: [
// статическое сообщение
[/^border$/, { message: 'используйте более короткое "b"' }],
// динамическое сообщение — получает заблокированный селектор
[/^border(?:-[btrlxy])?$/, {
// например, "border-y" → 'используйте более короткое "b-y"'
message: v => `используйте более короткое "${v.replace(/^border/, 'b')}"`
}],
]При использовании с @unocss/eslint-plugin, сообщение будет отображаться в выводе линтера:
"border" is in blocklist: используйте более короткое "b"Без плагина ESLint заблокированные утилиты просто исключаются из генерации CSS без какого-либо уведомления разработчика. Рекомендуется использовать @unocss/eslint-plugin вместе с правилами blocklist, чтобы получать сообщения с руководством к действию во время разработки.
Учёт вариантов
Blocklist проверяет селекторы как до, так и после удаления вариантов. Правило, блокирующее p-1, также заблокирует hover:p-1, md:p-1, dark:p-1 и так далее. Вам не нужно учитывать префиксы вариантов в шаблонах blocklist.
Поведение при слиянии
Массивы blocklist из всех пресетов и пользовательской конфигурации объединяются — они накапливаются и никогда не переопределяют друг друга. Утилита, заблокированная любым пресетом или пользовательской конфигурацией, останется заблокированной.
Ссылка на типы
type BlocklistValue = string | RegExp | ((selector: string) => boolean | null | undefined)
type BlocklistRule = BlocklistValue | [BlocklistValue, BlocklistMeta]
interface BlocklistMeta {
/**
* Пользовательское сообщение, объясняющее причину блокировки этого селектора.
*/
message?: string | ((selector: string) => string)
}Шаблоны использования чёрного списка
Ниже приведены распространённые шаблоны для эффективного использования чёрного списка.
Принудительное использование коротких псевдонимов
Если UnoCSS поддерживает несколько вариантов синтаксиса для одного и того же CSS-вывода, вы можете заблокировать многословную форму и предложить более короткую:
blocklist: [
// "border" → "b", "border-t" → "b-t"
[/^border(?:-[btrlxy])?$/, {
message: v => `use shorter "${v.replace(/^border/, 'b')}"`
}],
// "opacity-50" → "op-50"
// "backdrop-opacity-50" → "backdrop-op-50"
[/^(?:backdrop-)?opacity-(.+)$/, {
message: v => `use shorter "${v.replace(/opacity-/, 'op-')}"`
}],
// "whitespace-nowrap" → "ws-nowrap"
[/^whitespace-.+$/, {
message: v => `use shorter "${v.replace(/^whitespace-/, 'ws-')}"`
}],
// простые статические псевдонимы хорошо работают для замен «один-к-одному»
[/^flex-grow$/, { message: 'use shorter "grow"' }],
[/^flex-shrink$/, { message: 'use shorter "shrink"' }],
[/^inline-block$/, { message: 'use shorter "i-block"' }], // вы также можете ссылаться на свои пользовательские шорткаты
]Ограничение токенами дизайн-системы
Вы можете динамически создавать шаблоны blocklist на основе конфигурации вашей дизайн-системы, чтобы гарантировать использование только валидных токенов. Используйте негативный просмотр вперёд, чтобы разрешить валидные значения и заблокировать всё остальное:
import { theme } from './my-design-system'
// Хелпер для объединения ключей объекта в перечисление (OR) для регулярного выражения
const keys = (obj: Record<string, any>) => Object.keys(obj).join('|')
blocklist: [
// Разрешать только те семейства шрифтов, которые определены в дизайн-системе
[new RegExp(`^font-(?!(?:${keys(theme.fontFamily)}|\\$)$).+$`), {
message: `используйте семейства шрифтов из дизайн-системы: ${Object.keys(theme.fontFamily).join(', ')}`
}],
// Разрешать только те значения теней, которые определены в дизайн-системе
[new RegExp(`^shadow-(?!(?:${keys(theme.boxShadow)}|\\$)).+$`), {
message: `разрешены только значения теней из дизайн-системы.`
}],
]СОВЕТ
\\$ в негативной опережающей проверке позволяет пропускать ссылки на CSS-переменные (например, font-$myVar), так как они вычисляются во время выполнения и не могут быть проверены статически.
Преобразование произвольных единиц в значения шкалы
Если в вашем проекте используется стандартная шкала отступов UnoCSS (где 1 единица = 0.25rem = 4px), вы можете заблокировать использование произвольных значений px и rem и предлагать эквивалент из шкалы:
blocklist: [
// "mt-16px" → "mt-4"
// "p-[8px]" → "p-2"
// "w-2rem" → "w-8"
[/^.+-\[?[\d.]+(?:px|rem)\]?$/, {
message: (s) => {
// так как message() получает только строку совпавшего селектора, а не регулярное выражение,
// нам приходится выполнять сопоставление ещё раз, чтобы извлечь группы захвата из матчера blocklist
const m = s.match(/\[?(?<v>[\d.]+)(?<u>px|rem)\]?$/)!
const { v, u } = m.groups!
const scale = u === 'rem' ? +v * 4 : +v / 4
return `use spacing scale value: ${s.slice(0, -m[0].length)}${scale}`
}
}],
]Удаление лишних скобок
Квадратные скобки UnoCSS для произвольных значений [...] часто не требуются, если значение остается валидным и без них:
blocklist: [
// "w-[50%]" → "w-50%"
[/^(w|h|min-[wh]|max-[wh]|top|right|bottom|left)-\[\d+%\]$/, {
message: (v) => {
const value = v.match(/\[(\d+%)\]/)?.[1] || ''
return `use shorter ${v.replace(/-\[\d+%\]/, `-${value}`)}`
}
}],
// "outline-[#ff0000]" → "outline-#ff0000"
[/^[a-z-]+-\[#[0-9a-fA-F]{3,6}\]$/, {
message: v => `use shorter ${v.replace(/\[#/, '#').replace(/\]/, '')}`
}],
]Соблюдение соглашений
Блокируйте паттерны, которые нарушают архитектурные решения, принятые в рамках конкретного проекта:
blocklist: [
// Предотвращение избыточного брейкпойнта — если «sm» равен 0, он всегда активен
// в мобильном (mobile-first) адаптивном дизайне и его не нужно указывать
[/^sm:/, {
message: v => `брейкпоинт sm: избыточен, используйте "${v.replace(/^sm:/, '')}"`
}],
// Принудительное использование отдельных утилит вместо синтаксиса с косой чертой для прозрачности.
// Отдельные утилиты имеют больше шансов на повторное использование, чем комбинации со слешем,
// что помогает уменьшить итоговый размер CSS-бандла.
// "bg-red-500/50" → "bg-red-500 bg-op-50"
[/^(c|bg)-.+\/\d+$/, {
message: 'используйте отдельный класс прозрачности вместо синтаксиса со слешем (например, "bg-red bg-op-50").'
}],
// Разделение шорткатов на отдельные переиспользуемые свойства.
// Тот же принцип — раздельные утилиты уменьшают размер CSS-бандла.
// "size-4" → "w-4 h-4"
[/^size-(.+)$/, {
message: (v) => {
const size = v.match(/^size-(.+)$/)?.[1]
return `используйте "w-${size} h-${size}" для независимого управления`
}
}],
]