Read in:
Русский

yield_blocks: CSS и JS по страницам

yield_blocks собирает CSS (или JS) из компонентов, использованных на странице, и выводит его один раз, инлайн. На страницу попадают только стили тех компонентов, которые на ней есть.

Проблема, которую это решает

При сборке страницы из переиспользуемых компонентов у каждого компонента есть свой CSS. Глобальный файл стилей загружает всё на каждой странице. yield_blocks работает иначе: сканирует, какие компоненты есть на странице, и записывает только их CSS в один тег <style>.

Структура файлов

Компоненты хранятся рядом с файлами шаблонов. Каждый файл компонента содержит его блоки:

_layouts/
└── my-theme/
    ├── components/
    │   ├── button.html
    │   ├── card.html
    │   └── hero.html
    └── page.html

Структура файла компонента

Файл компонента содержит два вида блоков: блок стилей и HTML-блок. Оба следуют соглашению об именовании:

  • _style_имякомпонента — CSS компонента
  • _js_имякомпонента — JS компонента (опционально)
  • имякомпонента — HTML компонента
<!-- components/button.html -->
{{block _style_button()}}
.button { display: inline-flex; padding: 8px 16px; }
.button--primary { background: #0070f3; color: #fff; }
{{end}}

{{block button(label="Нажмите", variant="")}}
<button class="button{{if variant}} button--{{variant}}{{end}}">{{label}}</button>
{{end}}

Имя блока стилей начинается с _style_. HTML-блок использует простое имя компонента. Загрузчик сопоставляет их по общему суффиксу (button в _style_button и button).

Использование компонентов на странице

Шаблон страницы вызывает компоненты через {{yield}}. Импорты не нужны — загрузчик находит файлы компонентов автоматически.

<!-- pages/home.html -->
<html>
<head>
  <style>{{yield_blocks("_style_")}}</style>
</head>
<body>
  {{yield hero(title="Добро пожаловать")}}
  {{yield card(title="Возможности", body="...")}}
  {{yield button(label="Начать", variant="primary")}}
</body>
</html>

yield_blocks("_style_") сканирует все вызовы {{yield}} на странице, находит каждый блок, чьё имя начинается с _style_, и записывает их CSS в тег. Результат — один <style> только с тем, что использует эта страница.

Разместите <style>{{yield_blocks("_style_")}}</style> в <head>. Имена блоков определяются статическим анализом при загрузке шаблона — до начала рендеринга, — поэтому вызов корректно работает в <head> и предотвращает мелькание нестилизованного контента (FOUC) при первой отрисовке.

Синтаксис паттернов

yield_blocks принимает строку-префикс или регулярное выражение:

Паттерн Что совпадает
"_style_" Все блоки, чьё имя начинается с _style_
"/_style_.*/" То же, как регулярное выражение (в обёртке /)
"/_js_.*/" Все блоки, чьё имя начинается с _js_

Для CSS и JS используйте форму с префиксом — она короче и читается понятнее.

Для JS добавьте тег <script> аналогичным образом:

<script>{{yield_blocks("/_js_.*/")}}</script>

Транзитивные зависимости

Если card.html внутри себя вызывает button, загрузчик автоматически включает button.html. Перечислять зависимости вручную не нужно — загрузчик обходит полный граф компонентов.

Уникальность имён блоков и @lid / @did

Имена блоков в Jet глобальны для всех включённых файлов. Если hero.html и card.html оба определяют {{block hero()}}, второе определение молча перезапишет первое. В проектах с несколькими компонентами, которые разрабатываются независимо, это реальная проблема.

@lid решает её: перед разбором шаблона плейсхолдер автоматически заменяется значением, производным от пути к файлу. Никакой настройки не требуется.

Путь к файлу Значение @lid Значение @did
button.html button button
components/button.html components_button components-button
ui/nav/header.html ui_nav_header ui-nav-header

@lid и @didпеременные препроцессора: загрузчик подставляет их до того, как Jet разбирает шаблон. @lid (lodash id — подчёркивания) используется в именах блоков Jet. @did (dash id — дефисы) используется в BEM CSS-классах. Оба значения берутся из пути к файлу, а не из имени блока. Для вставки литерального @lid / @did в JS или CSS используйте @@lid / @@did.

Используйте @lid в именах блоков и @did в CSS-классах:

{{block _style_@lid()}}
.@did { display: inline-flex; padding: 8px 20px; }
.@did--primary { background: #0070f3; color: #fff; }
{{end}}

{{block @lid(label="Нажмите", variant="")}}
<button class="@did{{if variant}} @did--{{variant}}{{end}}">{{label}}</button>
{{end}}

Для button.html это раскрывается в _style_button, .button и button. Для components/button.html — в _style_components_button, .components-button и components_button. Паттерн yield_blocks("_style_") по-прежнему найдёт оба варианта, потому что префикс проверяется по уже раскрытому имени.

Экранирование. Если в выводе нужна буквальная строка @lid — например, как переменная JavaScript — напишите @@lid:

{{block _js_@lid()}}
var @@lid = document.querySelector('.@did-root');
@@lid.addEventListener('click', () => { ... });
{{end}}

После подстановки (для button.html):

{{block _js_button()}}
var @lid = document.querySelector('.button-root');
@lid.addEventListener('click', () => { ... });
{{end}}

@@lid превращается в @lid в выводе; @lid (одиночный @) — в значение, производное от пути файла.

Предупреждения

Два случая дают нефатальное предупреждение в логе предпросмотра шаблона:

  • Дублирование имени блока — два файла компонентов определяют блок с одним именем. Побеждает первое определение, второе игнорируется.
  • Невалидное регулярное выражение — паттерн в обёртке /, который не является корректным regexp. yield_blocks выводит пустую строку для этого вызова; остальная часть страницы рендерится нормально.

Ни одно из предупреждений не прерывает рендеринг. Если стили неожиданно пропали — проверьте лог предпросмотра шаблона.

Правила для CSS-блоков

Параметры блоков не передаются. yield_blocks вызывает каждый CSS-блок без аргументов. Все параметры получают нулевые/пустые значения. Блок вида {{block _style_@lid(theme="")}}{{if theme}}.box--{{theme}}...{{end}}{{end}} никогда не выведет условную часть. CSS-блоки должны быть самодостаточными — не ветвитесь на основе собственных параметров.

Глобальные функции работают нормально. Глобалы вроде asset() и note доступны внутри CSS-блоков, вызванных через yield_blocks, точно так же, как в любом другом блоке.

Не вызывайте соседние CSS-блоки вручную. Если CSS-блок внутри себя делает {{yield _style_other()}}, а _style_other тоже попадает под паттерн yield_blocks, этот CSS выведется дважды. Пусть сбором занимается yield_blocks — CSS-блоки не должны вызывать друг друга.

Полный пример

_layouts/theme/
├── components/
│   ├── button.html
│   ├── card.html
│   └── hero.html
└── page.html

components/hero.html:

{{block _style_hero()}}
.hero { padding: 80px 24px; text-align: center; }
.hero__title { font-size: 2.5rem; font-weight: 700; }
{{end}}

{{block hero(title="")}}
<section class="hero">
  <h1 class="hero__title">{{title}}</h1>
</section>
{{end}}

page.html:

<!DOCTYPE html>
<html>
<head>
  <style>{{yield_blocks("_style_")}}</style>
</head>
<body>
  {{yield hero(title=note.Title())}}
  {{yield card(title="О проекте", body=note.HTMLString())}}
</body>
</html>

На отрендеренной странице тег <style> содержит CSS из _style_hero и _style_card. _style_button отсутствует — на этой странице нет {{yield button(...)}}.

BEM-именование для блоков стилей

Соглашение _style_имяблока хорошо сочетается с BEM-именованием классов. BEM привязывает каждый класс к имени блока, поэтому когда yield_blocks собирает CSS из нескольких компонентов на одной странице, коллизий не возникает.

Компонент card с BEM-классами:

{{block _style_card()}}
.card { border: 1px solid #eee; border-radius: 6px; padding: 16px; }
.card__title { font-weight: bold; }
.card--featured { border-color: #0070f3; }
{{end}}

.card__title никогда не совпадёт с .hero__title — имя блока входит в каждый класс. Полное описание соглашения — в BEM-именование в шаблонах.

Статические файлы: asset()

Шаблон может ссылаться на статические файлы (JS, CSS, SVG, изображения), лежащие рядом с ним, через asset():

<script defer src="{{ asset("scripts.js") }}"></script>
<img src="{{ asset("logo.svg") }}" alt="logo">

asset() разрешает путь относительно директории самого шаблона. Если шаблон находится по пути _layouts/mesh/index.html, то {{ asset("scripts.js") }} отдаёт файл _layouts/mesh/scripts.js.

В продакшне URL содержит хеш для сброса кеша, чтобы браузеры подхватывали обновления немедленно. В режиме разработки хеш опускается для читаемости.

Рядом с шаблоном можно разместить любой статический файл — JS, SVG, изображения, шрифты. Движок отдаёт их автоматически, без шага сборки и дополнительной конфигурации.

Пример структуры шаблона:

_layouts/
└── mesh/
    ├── _blocks.html
    ├── index.html
    ├── scripts.js      ← {{ asset("scripts.js") }}
    └── logo.svg        ← {{ asset("logo.svg") }}

Отличие от yield_blocks: тот собирает инлайновые CSS/JS из блоков компонентов во время рендеринга. asset() используется, когда файл является самостоятельным статическим ресурсом, которому шаблонная обработка не нужна.

Смотрите также