CSS nesting (вложенные правила) представляет собой синтаксическое расширение, позволяющее описывать иерархию селекторов прямо внутри родительского блока. Это приближает CSS к структурам препроцессоров (Sass, Less), но при этом сохраняет нативный формат спецификации, который постепенно стандартизируется.
В контексте сборщика esbuild поддержка CSS nesting реализуется через трансформацию входного CSS в валидный плоский CSS, совместимый с текущими браузерами. Важно учитывать, что поведение зависит от версии сборщика и выбранной конфигурации пайплайна, так как часть CSS-функциональности может обрабатываться плагинами или внешними трансформерами.
Типичный пример вложенности:
.card {
padding: 16px;
.title {
font-size: 18px;
font-weight: 600;
}
&:hover {
background: #f5f5f5;
}
}
После трансформации результат эквивалентен классическому CSS:
.card {
padding: 16px;
}
.card .title {
font-size: 18px;
font-weight: 600;
}
.card:hover {
background: #f5f5f5;
}
Ключевым элементом обработки является разрешение вложенных селекторов
относительно родительского контекста. Символ &
сохраняет возможность ссылаться на текущий селектор без потери
специфичности.
В пайплайне esbuild CSS-файлы проходят стадию разбора и нормализации. В зависимости от версии:
Типичная конфигурация с плагином:
import esbuild from "esbuild";
await esbuild.build({
entryPoints: ["src/index.css"],
bundle: true,
outfile: "dist/style.css",
loader: {
".css": "css"
}
});
При подключении PostCSS-плагины для nesting часто используются дополнительно:
import postcss from "postcss";
import postcssNested from "postcss-nested";
const nestedPlugin = {
name: "css-nesting",
setup(build) {
build.onLoad({ filter: /\.css$/ }, async (args) => {
const fs = await import("fs");
const source = await fs.promises.readFile(args.path, "utf8");
const result = await postcss([postcssNested]).process(source, {
from: args.path
});
return {
contents: result.css,
loader: "css"
};
});
}
};
Такая схема позволяет использовать полноценный nesting даже при отсутствии нативной поддержки в конкретной версии сборщика.
CSS nesting в сборке имеет несколько характерных ограничений:
&Особое внимание требуется при использовании комбинации вложенности и медиа-запросов:
.card {
padding: 16px;
@media (max-width: 768px) {
padding: 12px;
.title {
font-size: 16px;
}
}
}
После обработки структура превращается в набор независимых правил:
.card {
padding: 16px;
}
@media (max-width: 768px) {
.card {
padding: 12px;
}
.card .title {
font-size: 16px;
}
}
Такое преобразование критично для предсказуемости каскада.
CSS Cascade Layers (@layer) вводят механизм управления
приоритетами стилей на уровне слоёв, а не только специфичности
селекторов. Это позволяет выстраивать архитектуру CSS в виде логических
уровней: reset, base, components, utilities.
В рамках esbuild поддержка @layer связана с задачами
объединения и корректного упорядочивания правил при бандлинге нескольких
файлов.
@layer reset, base, components;
@layer reset {
* {
margin: 0;
padding: 0;
}
}
@layer base {
body {
font-family: system-ui;
}
}
@layer components {
.button {
padding: 10px 16px;
}
}
Слои определяют порядок применения правил независимо от специфичности селекторов внутри них.
При объединении CSS из нескольких модулей сборщик сталкивается с задачей:
@layerПример входных файлов:
fileA.css
@layer base {
h1 {
font-size: 24px;
}
}
fileB.css
@layer base {
h2 {
font-size: 20px;
}
}
После бандлинга результат должен сохранять единый слой:
@layer base {
h1 {
font-size: 24px;
}
h2 {
font-size: 20px;
}
}
При этом порядок файлов влияет на итоговую композицию, если отсутствует явное объявление порядка слоёв.
Системы сборки, включая esbuild, опираются на правило: порядок
@layer определяется либо первой декларацией, либо явным
списком.
@layer reset, base, components, utilities;
Если такой список присутствует, дальнейшие определения слоёв должны следовать ему. При объединении CSS из разных модулей важно, чтобы все файлы придерживались одного порядка, иначе итоговая каскадная модель может стать непредсказуемой.
Комбинация @layer и CSS nesting формирует более сложную
структуру преобразования.
@layer components {
.card {
padding: 16px;
.title {
font-size: 18px;
}
&:hover {
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
}
}
После трансформации логика слоёв сохраняется, но вложенность разворачивается:
@layer components {
.card {
padding: 16px;
}
.card .title {
font-size: 18px;
}
.card:hover {
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
}
Слой остаётся единым контейнером для всех производных правил.
При сборке нескольких CSS-файлов esbuild выполняет конкатенацию с учётом:
@layerПример конфликтного сценария:
/* a.css */
@layer base {
body { line-height: 1.5; }
}
/* b.css */
@layer base {
body { line-height: 1.6; }
}
Результат зависит от порядка подключения файлов, если не применяется дополнительная нормализация через плагин. Это делает критичным контроль порядка entry points в сборке.
При использовании CSS nesting и @layer в одном проекте с
esbuild часто применяется архитектурный подход разделения
ответственности:
Такой подход позволяет минимизировать влияние специфичности и упростить предсказуемость каскада.
Пример структуры:
@layer reset, base, components, utilities;
@layer reset {
/* сброс */
}
@layer base {
/* базовые стили */
}
@layer components {
/* компоненты */
}
@layer utilities {
/* утилиты */
}
При сборке через esbuild следует учитывать несколько системных особенностей:
@layer не изменяет специфичность, а только порядок
примененияОсобенно важно учитывать, что итоговый CSS после сборки становится линейным текстом, и любые логические конструкции существуют только до стадии бандлинга.
Использование nesting и @layer в рамках esbuild часто
сопровождает модульный подход к стилям, где каждый компонент или пакет
содержит собственный CSS-фрагмент.
@layer components {
.modal {
display: flex;
.header {
font-weight: 600;
}
.body {
padding: 16px;
}
}
}
После сборки такие блоки объединяются в единый CSS-граф, где:
После обработки CSS сборщиком исходная структура теряет вложенность, что влияет на отладку:
@layerИспользование source maps в esbuild помогает восстановить соответствие между вложенными структурами и финальными селекторами, что критично при сложных каскадных конфигурациях.