При разработке крупных проектов или интеграции готовых UI-библиотек часто возникает проблема конфликта имён CSS-классов. Стили одной библиотеки могут неожиданно повлиять на элементы другой, даже если вы старались использовать уникальные имена. Обычно эту проблему решают через CSS Modules или Scoped CSS.
Существует и другой, менее очевидный подход: добавить единый класс-контейнер ко всем селекторам CSS-файла. Например, если все правила начинаются с .my-app, то они будут применяться только к элементам внутри блока с этим классом. Это эффективно изолирует стили, не требуя переписывания всей библиотеки.
В этой статье мы разберём PHP-скрипт, который автоматически добавляет заданный класс-префикс ко всем селекторам в CSS-файле. Такой инструмент может быть полезен, когда вы подключаете сторонний CSS (например, Materialize, Bootstrap) и хотите ограничить его действие определённой частью страницы.
Постановка задачи
Исходный CSS-файл (materialize.css) содержит селекторы разных типов:
|
1 2 3 4 5 6 7 8 9 10 11 |
.btn { background: blue; } .card .title { font-size: 1.2rem; } @media (max-width: 600px) { .container { width: 100%; } } |
Нужно получить файл, в котором каждый селектор (кроме тех, что внутри @keyframes, так как они не относятся к DOM-элементам) будет начинаться с указанного класса, например .mlds. При этом важно сохранить вложенность и медиа-правила:
|
1 2 3 4 5 6 7 8 9 10 11 |
.mlds .btn { background: blue; } .mlds .card .title { font-size: 1.2rem; } @media (max-width: 600px) { .mlds .container { width: 100%; } } |
Почему не Autoprefixer?
Созвучное название может внести путаницу. Autoprefixer добавляет вендорные префиксы к CSS-свойствам (-webkit-, -moz- и т.д.). Наша задача – изменить селекторы, а не свойства. Это позволяет создать неймспейс для целой библиотеки. Такой приём часто называют «css namespacing» или «scoping через родительский класс».
Обзор решения
Я буду использовать библиотеку sabberworm/php-css-parser, которая разбирает CSS в объектную модель, позволяет изменять узлы и затем рендерить обратно в строку. Скрипт:
- Загружает исходный CSS.
- Парсит его в дерево.
- Рекурсивно обходит все блоки правил (
DeclarationBlock). - Для каждого селектора добавляет перед ним префикс-класс и пробел.
- Пропускает
@keyframes(их селекторы не нужно префиксировать). - Сохраняет результат в новый файл.
Код с пояснениями
Я использую composer для подключения Sabberworm. Потому в начале файла появляется строка с vendor/autoload.php. Если вы подключаете Sabberworm напрямую, то у вас этот момент может отличаться.
|
1 2 3 4 |
// Если всё же вы выбрали composer: composer init // Добавить Sabberworm composer require sabberworm/php-css-parser |
input и output файлы определяют какой файл нужно править и куда потом сохранить правки.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
<?php require __DIR__ . '/vendor/autoload.php'; use Sabberworm\CSS\Parser; use Sabberworm\CSS\RuleSet\DeclarationBlock; use Sabberworm\CSS\OutputFormat; $inputFile = __DIR__ . '/materialize.css'; $outputFile = __DIR__ . '/materialize-prefixed.css'; $prefix = '.mlds'; if (!file_exists($inputFile)) { die("Файл не найден: $inputFile\n"); } $css = file_get_contents($inputFile); $parser = new Parser($css); $document = $parser->parse(); // Рекурсивный обход дерева CSS function addPrefixToSelectors($list, $prefix) { foreach ($list->getContents() as $item) { // Пропускаем keyframes – они содержат имена анимаций, а не селекторы элементов if ($item instanceof \Sabberworm\CSS\CSSList\KeyFrame) { continue; } if ($item instanceof DeclarationBlock) { $selectors = $item->getSelectors(); $newSelectors = []; foreach ($selectors as $selector) { $selectorString = $selector->getSelector(); // Добавляем префикс, если это не at-правило (в DeclarationBlock их нет, но проверка не помешает) if (strpos($selectorString, '@') !== 0) { $newSelectors[] = $prefix . ' ' . $selectorString; } else { $newSelectors[] = $selectorString; } } $item->setSelectors($newSelectors); } // Рекурсивно обрабатываем вложенные списки (например, @media, @supports) if ($item instanceof \Sabberworm\CSS\CSSList\CSSList) { addPrefixToSelectors($item, $prefix); } } } addPrefixToSelectors($document, $prefix); // Здесь можно использовать также OutputFormat::createCompact() // чтобы получить сразу минифицированный вывод. $outputFormat = OutputFormat::createPretty(); file_put_contents($outputFile, $document->render($outputFormat)); echo "Готово! Файл сохранён: $outputFile\n"; |
Теперь в примере — достаточно обернуть часть страницы в <div class="mlds">, и стили Materialize будут действовать только внутри этого контейнера, не конфликтуя с остальными.
Ограничения и нюансы
- Сложные селекторы – скрипт просто добавляет пробел и класс в начало строки. Для селекторов вида
div > .class + spanэто даст.mlds div > .class + span, что корректно. - Селекторы с запятой – парсер библиотеки разбивает их на отдельные объекты, поэтому каждый получит префикс.
:hostи теневой DOM – если в CSS есть псевдоклассы для веб-компонентов, добавление класса может сломать логику. В таких случаях префиксация не рекомендуется.@keyframesне префиксируется – это осознанное решение. Если нужно изолировать имена анимаций, потребуется отдельный подход (переименование).- Вес селекторов – добавление родительского класса увеличивает специфичность всех правил. Это редко приводит к проблемам, но о нюансе стоит помнить.
