Глубокое погружение в правило CSS @apply. Узнайте, что это было, почему его упразднили, и изучите современные альтернативы для применения миксинов и композиции стилей.
Правило CSS @apply: взлёт и падение нативных миксинов и современные альтернативы
В постоянно развивающемся мире веб-разработки поиск более чистого, поддерживаемого и повторно используемого кода является постоянной задачей. В течение многих лет разработчики полагались на CSS-препроцессоры, такие как Sass и Less, чтобы привнести программную мощь в таблицы стилей. Одной из самых любимых функций этих инструментов является миксин — способ определения многократно используемого блока CSS-объявлений. Это привело к естественному вопросу: можем ли мы получить эту мощную функцию нативно в CSS? Какое-то время казалось, что ответ — да, и имя ему было @apply.
Правило @apply было многообещающим предложением, которое стремилось привнести функциональность, подобную миксинам, непосредственно в браузер, используя мощь пользовательских свойств CSS (CSS Custom Properties). Оно обещало будущее, в котором мы могли бы определять повторно используемые фрагменты стилей на чистом CSS и применять их где угодно, даже динамически обновляя их с помощью JavaScript. Однако, если вы современный разработчик, вы не найдёте @apply ни в одном стабильном браузере. В конечном итоге это предложение было отозвано из официальной спецификации CSS.
Эта статья представляет собой всестороннее исследование правила CSS @apply. Мы разберём, что это было, какой мощный потенциал оно несло для композиции стилей, сложные причины его упразднения и, что наиболее важно, современные, готовые к использованию в продакшене альтернативы, которые решают те же проблемы в сегодняшней экосистеме разработки.
Что такое правило CSS @apply?
По своей сути, правило @apply было разработано для того, чтобы взять набор CSS-объявлений, хранящихся в пользовательском свойстве, и «применить» их внутри CSS-правила. Это позволяло разработчикам создавать то, что по сути являлось «наборами свойств» или «наборами правил», которые можно было повторно использовать в нескольких селекторах, воплощая принцип «Не повторяйся» (Don't Repeat Yourself, DRY).
Концепция была построена на основе пользовательских свойств CSS (часто называемых CSS-переменными). В то время как мы обычно используем пользовательские свойства для хранения одиночных значений, таких как цвет (--brand-color: #3498db;) или размер (--font-size-md: 16px;), предложение по @apply расширяло их возможности до хранения целых блоков объявлений.
Предлагаемый синтаксис
Синтаксис был простым и интуитивно понятным для всех, кто знаком с CSS. Сначала вы определяли пользовательское свойство, содержащее блок CSS-объявлений, заключённый в фигурные скобки {}.
:root {
--primary-button-styles: {
background-color: #007bff;
color: #ffffff;
border: 1px solid transparent;
padding: 0.5rem 1rem;
font-size: 1rem;
border-radius: 0.25rem;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
};
}
Затем, внутри любого CSS-правила, вы могли использовать at-правило @apply, чтобы внедрить весь этот блок стилей:
.btn-primary {
@apply --primary-button-styles;
}
.form-submit-button {
@apply --primary-button-styles;
margin-top: 1rem; /* You could still add other styles */
}
В этом примере и .btn-primary, и .form-submit-button унаследовали бы полный набор стилей, определённых в --primary-button-styles. Это было значительным отличием от стандартной функции var(), которая может подставлять только одно значение в одно свойство.
Ключевые предполагаемые преимущества
- Повторное использование кода: Самым очевидным преимуществом было устранение повторений. Общие паттерны, такие как стили кнопок, макеты карточек или блоки уведомлений, можно было определить один раз и применять повсеместно.
- Улучшенная поддерживаемость: Чтобы обновить внешний вид всех основных кнопок, вам нужно было бы отредактировать только пользовательское свойство
--primary-button-styles. Изменение распространилось бы на каждый элемент, где оно было применено. - Динамическое создание тем: Поскольку это было основано на пользовательских свойствах, эти миксины можно было динамически изменять с помощью JavaScript, что открывало мощные возможности для создания тем во время выполнения, которые препроцессоры (работающие во время компиляции) предложить не могут.
- Сокращение разрыва: Оно обещало перенести столь любимую функцию из мира препроцессоров в нативный CSS, уменьшая зависимость от инструментов сборки для этой конкретной функциональности.
Перспективы @apply: нативные миксины и композиция стилей
Потенциал @apply выходил далеко за рамки простого повторного использования стилей. Он открывал две мощные концепции для архитектуры CSS: нативные миксины и декларативную композицию стилей.
Нативный ответ миксинам препроцессоров
В течение многих лет Sass был золотым стандартом для миксинов. Давайте сравним, как Sass достигает этого, с тем, как предполагалось работало @apply.
Типичный миксин Sass:
@mixin flexible-center {
display: flex;
justify-content: center;
align-items: center;
}
.hero-banner {
@include flexible-center;
height: 100vh;
}
.modal-content {
@include flexible-center;
flex-direction: column;
}
Эквивалент с @apply:
:root {
--flexible-center: {
display: flex;
justify-content: center;
align-items: center;
};
}
.hero-banner {
@apply --flexible-center;
height: 100vh;
}
.modal-content {
@apply --flexible-center;
flex-direction: column;
}
Синтаксис и опыт разработки были поразительно похожи. Однако ключевое различие заключалось в исполнении. Миксин @mixin в Sass обрабатывается на этапе сборки, создавая статический CSS. Правило @apply должно было обрабатываться браузером во время выполнения. Это различие было одновременно его величайшей силой и, как мы увидим, его окончательным падением.
Декларативная композиция стилей
@apply позволило бы разработчикам создавать сложные компоненты, составляя их из более мелких, одноцелевых фрагментов стилей. Представьте себе создание библиотеки UI-компонентов, где у вас есть базовые блоки для типографики, макета и внешнего вида.
:root {
--typography-body: {
font-family: 'Inter', sans-serif;
font-size: 16px;
line-height: 1.5;
color: #333;
};
--card-layout: {
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
};
--theme-light: {
background-color: #ffffff;
border: 1px solid #ddd;
};
--theme-dark: {
background-color: #2c3e50;
border: 1px solid #444;
color: #ecf0f1;
};
}
.article-card {
@apply --typography-body;
@apply --card-layout;
@apply --theme-light;
}
.user-profile-card.dark-mode {
@apply --typography-body;
@apply --card-layout;
@apply --theme-dark;
}
Этот подход является в высшей степени декларативным. CSS для .article-card чётко описывает его состав: он имеет типографику основного текста, макет карточки и светлую тему. Это делает код более легким для чтения и понимания.
Динамическая суперсила
Самой убедительной особенностью была его динамичность во время выполнения. Поскольку --card-theme могло быть обычным пользовательским свойством, вы могли бы заменять целые наборы правил с помощью JavaScript.
/* CSS */
.user-profile-card {
@apply --typography-body;
@apply --card-layout;
@apply var(--card-theme, --theme-light); /* Apply a theme, defaulting to light */
}
/* JavaScript */
const themeToggleButton = document.getElementById('theme-toggle');
themeToggleButton.addEventListener('click', () => {
const root = document.documentElement;
const isDarkMode = root.style.getPropertyValue('--card-theme') === '--theme-dark';
if (isDarkMode) {
root.style.setProperty('--card-theme', '--theme-light');
} else {
root.style.setProperty('--card-theme', '--theme-dark');
}
});
Этот гипотетический пример показывает, как можно было бы переключать компонент между светлой и тёмной темой, изменяя одно пользовательское свойство. Браузеру затем пришлось бы переоценить правило @apply и на лету заменить большой кусок стилей. Это была невероятно мощная идея, но она также намекала на огромную сложность, скрывающуюся под поверхностью.
Большие дебаты: почему @apply удалили из спецификации CSS?
С такой убедительной концепцией, почему @apply исчезло? Решение об его удалении не было принято легкомысленно. Оно стало результатом долгих и сложных обсуждений в Рабочей группе CSS (CSSWG) и среди производителей браузеров. Причины сводились к серьезным проблемам с производительностью, сложностью и фундаментальными принципами CSS.
1. Неприемлемые последствия для производительности
Это была основная причина его падения. CSS спроектирован так, чтобы быть невероятно быстрым и эффективным. Движок рендеринга браузера может парсить таблицы стилей, строить CSSOM (объектную модель CSS) и применять стили к DOM в высокооптимизированной последовательности. Правило @apply угрожало разрушить эти оптимизации.
- Парсинг и валидация: Когда браузер встречает пользовательское свойство, такое как
--main-color: blue;, ему не нужно проверять значение `blue`, пока оно фактически не будет использовано в свойстве, напримерcolor: var(--main-color);. Однако с@applyбраузеру пришлось бы парсить и валидировать целый блок произвольных CSS-объявлений внутри пользовательского свойства. Это гораздо более трудоёмкая задача. - Сложность каскадирования: Самой большой проблемой было понять, как
@applyбудет взаимодействовать с каскадом. Когда вы применяете (@apply) блок стилей, где эти стили вписываются в каскад? Имеют ли они ту же специфичность, что и правило, в котором они находятся? Что произойдёт, если свойство, применённое через@apply, будет позже переопределено другим стилем? Это создавало проблему «позднего срабатывания» каскада, которая была вычислительно дорогой и трудной для последовательного определения. - Бесконечные циклы и циклические зависимости: Это вводило возможность циклических ссылок. Что если
--mixin-aприменял--mixin-b, который, в свою очередь, применял--mixin-a? Обнаружение и обработка таких случаев во время выполнения добавили бы значительные накладные расходы для CSS-движка.
По сути, @apply требовало от браузера выполнения значительного объёма работы, которая обычно выполняется инструментами сборки во время компиляции. Выполнение этой работы эффективно во время выполнения при каждом пересчёте стилей было сочтено слишком затратным с точки зрения производительности.
2. Нарушение гарантий каскада
Каскад CSS — это предсказуемая, хотя иногда и сложная, система. Разработчики полагаются на его правила специфичности, наследования и порядка исходного кода, чтобы рассуждать о своих стилях. Правило @apply вводило уровень косвенности, который значительно усложнял эти рассуждения.
Рассмотрим этот сценарий:
:root {
--my-mixin: {
color: blue;
};
}
div {
@apply --my-mixin; /* color is blue */
color: red; /* color is now red */
}
Это кажется достаточно простым. Но что, если порядок был бы обратным?
div {
color: red;
@apply --my-mixin; /* Does this override the red? */
}
Рабочей группе CSS пришлось решать: ведёт ли себя @apply как сокращённое свойство, которое раскрывается на месте, или как набор объявлений, которые внедряются со своим собственным порядком в исходном коде? Эта двусмысленность подрывала основную предсказуемость CSS. Это часто описывали как «магию» — термин, который разработчики используют для поведения, которое нелегко понять или отладить. Введение такой «магии» в ядро CSS было серьёзной философской проблемой.
3. Проблемы с синтаксисом и парсингом
Сам синтаксис, хотя и казался простым, создавал проблемы. Разрешение произвольного CSS внутри значения пользовательского свойства означало, что CSS-парсер должен был бы стать намного сложнее. Ему пришлось бы обрабатывать вложенные блоки, комментарии и потенциальные ошибки внутри самого определения свойства, что было значительным отступлением от того, как были спроектированы пользовательские свойства (хранить по сути строку до момента подстановки).
В конечном счёте, консенсус заключался в том, что затраты на производительность и сложность значительно перевешивали преимущества для удобства разработчиков, особенно когда уже существовали или были на горизонте другие решения.
Наследие @apply: современные альтернативы и лучшие практики
Мечта о повторно используемых фрагментах стилей в CSS далека от смерти. Проблемы, которые @apply пыталось решить, всё ещё очень реальны, и сообщество разработчиков с тех пор приняло несколько мощных, готовых к использованию в продакшене альтернатив. Вот что вам следует использовать сегодня.
Альтернатива 1: Освойте пользовательские свойства CSS (как и предполагалось)
Самое прямое, нативное решение — использовать пользовательские свойства CSS по их прямому назначению: для хранения одиночных, повторно используемых значений. Вместо создания миксина для кнопки вы создаёте набор пользовательских свойств, которые определяют тему кнопки. Этот подход является мощным, производительным и полностью поддерживается всеми современными браузерами.
Пример: Создание компонента с помощью пользовательских свойств
:root {
--btn-padding-y: 0.5rem;
--btn-padding-x: 1rem;
--btn-font-size: 1rem;
--btn-border-radius: 0.25rem;
--btn-transition: color .15s ease-in-out, background-color .15s ease-in-out;
}
.btn {
/* Structural styles */
display: inline-block;
padding: var(--btn-padding-y) var(--btn-padding-x);
font-size: var(--btn-font-size);
border-radius: var(--btn-border-radius);
transition: var(--btn-transition);
cursor: pointer;
text-align: center;
border: 1px solid transparent;
}
.btn-primary {
/* Theming via custom properties */
--btn-bg: #007bff;
--btn-color: #ffffff;
--btn-hover-bg: #0056b3;
background-color: var(--btn-bg);
color: var(--btn-color);
}
.btn-primary:hover {
background-color: var(--btn-hover-bg);
}
.btn-secondary {
--btn-bg: #6c757d;
--btn-color: #ffffff;
--btn-hover-bg: #5a6268;
background-color: var(--btn-bg);
color: var(--btn-color);
}
.btn-secondary:hover {
background-color: var(--btn-hover-bg);
}
Этот подход даёт вам компоненты с возможностью темизации и поддержкой, используя нативный CSS. Структура определяется в .btn, а тема (та часть, которую вы могли бы поместить в правило @apply) контролируется пользовательскими свойствами, ограниченными областью видимости модификаторов, таких как .btn-primary.
Альтернатива 2: Utility-First CSS (например, Tailwind CSS)
Фреймворки с подходом "utility-first", такие как Tailwind CSS, довели концепцию композиции стилей до её логического завершения. Вместо создания классов компонентов в CSS, вы составляете стили непосредственно в вашем HTML, используя маленькие, одноцелевые утилитные классы.
Интересно, что у Tailwind CSS есть своя собственная директива @apply. Крайне важно понимать, что это НЕ нативное правило CSS @apply. @apply в Tailwind — это функция времени сборки, которая работает в рамках его экосистемы. Она считывает ваши утилитные классы и компилирует их в статический CSS, избегая всех проблем с производительностью во время выполнения, которые были у нативного предложения.
Пример: Использование @apply в Tailwind
/* In your CSS file processed by Tailwind */
.btn-primary {
@apply bg-blue-500 text-white font-bold py-2 px-4 rounded hover:bg-blue-700;
}
/* In your HTML */
<button class="btn-primary">
Primary Button
</button>
Здесь @apply в Tailwind берёт список утилитарных классов и создаёт новый класс компонента, .btn-primary. Это обеспечивает тот же опыт разработки по созданию повторно используемых наборов стилей, но делает это безопасно во время компиляции.
Альтернатива 3: Библиотеки CSS-in-JS
Для разработчиков, работающих в JavaScript-фреймворках, таких как React, Vue или Svelte, библиотеки CSS-in-JS (например, Styled Components, Emotion) предлагают ещё один мощный способ достижения композиции стилей. Они используют собственную модель композиции JavaScript для создания стилей.
Пример: Миксины в Styled Components (React)
import styled, { css } from 'styled-components';
// Define a mixin using a template literal
const buttonBaseStyles = css`
background-color: #007bff;
color: #ffffff;
border: 1px solid transparent;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
cursor: pointer;
`;
// Create a component and apply the mixin
const PrimaryButton = styled.button`
${buttonBaseStyles}
&:hover {
background-color: #0056b3;
}
`;
// Another component reusing the same base styles
const SubmitButton = styled.input.attrs({ type: 'submit' })`
${buttonBaseStyles}
margin-top: 1rem;
`;
Это использует всю мощь JavaScript для создания повторно используемых, динамических и изолированных стилей, решая проблему DRY в рамках компонентно-ориентированной парадигмы.
Альтернатива 4: CSS-препроцессоры (Sass, Less)
Давайте не будем забывать об инструментах, с которых всё началось. Sass и Less по-прежнему невероятно мощны и широко используются. Их функциональность миксинов является зрелой, многофункциональной (они могут принимать аргументы) и абсолютно надёжной, потому что, как и @apply в Tailwind, они работают во время компиляции.
Для многих проектов, особенно тех, что не построены на тяжёлом JavaScript-фреймворке, препроцессор по-прежнему является самым простым и эффективным способом управления сложными, повторно используемыми стилями.
Заключение: уроки, извлечённые из эксперимента с @apply
История правила CSS @apply — это увлекательный пример эволюции веб-стандартов. Она представляет собой смелую попытку внедрить любимую функцию разработчиков в нативную платформу. Его окончательный отзыв был не провалом идеи, а свидетельством приверженности Рабочей группы CSS производительности, предсказуемости и долгосрочному здоровью языка.
Ключевые выводы для современных разработчиков:
- Используйте пользовательские свойства CSS для значений, а не для наборов правил. Применяйте их для создания мощных систем темизации и поддержания консистентности дизайна.
- Выбирайте правильный инструмент для композиции. Проблема, которую пыталось решить
@apply— композиция стилей — лучше всего решается специальными инструментами, которые работают во время сборки (например, Tailwind CSS или Sass) или в контексте компонента (например, CSS-in-JS). - Понимайте «почему» за веб-стандартами. Знание того, почему функция, подобная
@apply, была отклонена, даёт нам более глубокое понимание сложностей инженерии браузеров и фундаментальных принципов CSS, таких как каскад.
Хотя мы, возможно, никогда не увидим нативное правило @apply в CSS, его дух живёт. Желание иметь более модульный, компонентно-ориентированный и DRY-подход к стилизации сформировало современные инструменты и лучшие практики, которые мы используем каждый день. Веб-платформа продолжает развиваться, и такие функции, как вложенность CSS, @scope и каскадные слои, предоставляют новые, нативные способы написания более организованного и поддерживаемого CSS. Путь к лучшему опыту стилизации продолжается, и уроки, извлечённые из таких экспериментов, как @apply, прокладывают дорогу вперёд.