nuckty
EN
Все разделы →

HTML на выходе

Движок сериализует дерево BEMJSON в минимальный HTML5 без служебных отступов. Переносы из текста, кода и raw сохраняются, поэтому результат не обязательно состоит из одной строки.

Минимизация по 30 правилам

Правила описывают сериализацию дерева. Допустимость структуры зависит от входного дерева и шаблонов. Проверяйте конкретный артефакт командой валидации; тесты минимизации не подтверждают валидность любого документа.

Обёртка документа

  1. При document: true первыми байтами добавляется <!doctype html>.
  2. Открывающий <html> опускается, если у него нет выводимых атрибутов или классов и начальный комментарий не переместится за пределы корневого элемента.
  3. Открывающий <head> опускается, если у него нет выводимых атрибутов или классов и он пуст либо начинается с элемента.
  4. Открывающий <body> опускается, если у него нет выводимых атрибутов или классов и он пуст либо начинается не с пробельного ASCII-символа, комментария или элемента meta, noscript, link, script, style, template.
  5. При document: true движок добавляет два перевода строки. Фрагмент без этой опции их не получает. Граница последнего абзаца или пустой обёртки сохраняется явно, если без неё эти переводы строки попали бы в другой элемент.

Сериализатор рассматривает готовые дочерние узлы после разворачивания массивов и фрагментов tag: false; пустые позиции и пустые строки ничего не выводят. Атрибутами считаются в том числе БЭМ-классы и классы примесей, явный cls и сгенерированный data-bem: открывающий тег с любым из них сохраняется. При отдельном рендеринге head/body обёртки сохраняются, поскольку родитель неизвестен. Доверенный raw выводится без изменений; соседний тег сохраняется, если содержимое raw не позволяет доказать безопасность опускания.

Пустые элементы

area, base, br, col, embed, hr, img, input, link, meta, param, source, track, wbr:

  1. <имя атрибуты> — без />.
  2. Внутри SVG пустые path, circle, ellipse, line, polygon, polyline, rect, use и stop используют />. У других элементов SVG закрывающий тег сохраняется. Для потомков foreignObject действуют правила HTML; вложенный svg снова включает правила SVG.

В поддерживаемый набор пустых тегов также входит устаревший param.

При сопоставлении имён тегов и логических атрибутов регистр ASCII-букв не учитывается; авторское написание сохраняется. Поэтому IMG остаётся пустым тегом, а смешанный регистр SVG и foreignObject не меняет контекст сериализации. Имена атрибутов с Unicode не превращаются в известные логические имена.

Логические атрибуты

muted, autoplay, playsinline, async, defer, disabled, checked, selected, …:

  1. Только имя, без = и значения.

У hidden есть строковые значения: например, until-found сохраняется. В attrs значение hidden: true выводит атрибут без значения, а hidden: false опускает его.

Кавычки у атрибутов

Сначала движок экранирует символы HTML, затем выбирает внешние кавычки для уже закодированного значения:

  1. значение пустое → alt="" (кавычки обязательны);
  2. кавычки нужны, если значение содержит любой из символов: пробельный символ, ", ', `, =, <, >;
  3. иначе — без кавычек: имя=значение.
  4. Если кавычки нужны, используются двойные "...".

Запятая в значении не требует кавычек — data-tags=a,b остаётся без них.

Исходные ", < и > к этому моменту превращены в HTML-сущности и сами по себе не требуют внешних кавычек. Например, значение из одного " может выводиться как data-x=&quot;. Генерируемый data-bem всегда берётся в кавычки, как описано ниже.

Опускание закрывающих тегов

  1. </html> и </body> опускаются, если следующий комментарий не переместится к другому родителю. </head> сохраняется перед пробельным ASCII-символом или комментарием. На границе с непрозрачным raw тег сохраняется, если безопасность опускания неизвестна.
  2. </li> опускается перед следующим li и в конце ul, ol или menu. Правило едино для списков Markdown и шаблонов. Поле tightList сохранено для совместимости и больше не меняет сериализацию.
  3. </td> и </th> опускаются перед следующим td/th или в конце строки.
  4. </tr> опускается перед следующим tr или в конце таблицы либо группы строк.
  5. </thead> опускается непосредственно перед tbody или tfoot.
  6. </tbody> опускается перед следующим tbody или tfoot, либо в конце таблицы. </tfoot> опускается в конце таблицы.
  7. </p> опускается перед тегами из стандарта HTML и в конце известного HTML-родителя, кроме a, audio, del, ins, map, noscript, video или автономного пользовательского элемента. Он сохраняется перед текстом, пробелами, строчными элементами и непрозрачным raw, в инородном содержимом и во фрагменте с неизвестным родителем. Служебные переводы строки остаются вне последнего абзаца.
  8. Остальные необязательные закрывающие теги опускаются по таблице ниже. Обязательные закрывающие теги сохраняются.
Закрывающий тегОпускается перед
dtdt или dd
dddt, dd или концом группы определений
rt, rprt, rp или концом ruby
optionoption, optgroup, hr или концом контейнера вариантов
optgroupoptgroup, hr или концом родителя внутри select
caption, colgroupВсем, кроме пробельного ASCII-символа или комментария; перед неизвестным raw тег сохраняется

Открывающий <colgroup> без выводимых атрибутов опускается, только если первый дочерний узел — col, а предыдущий соседний узел — не другой colgroup с опущенным закрывающим тегом. Открывающий <tbody> без выводимых атрибутов опускается, только если первый дочерний узел — tr, а предыдущий соседний узел — не thead, tbody или tfoot с опущенным закрывающим тегом. Пустые группы и группы, начинающиеся с текста или комментария, сохраняют открывающий тег. Эти ограничения не дают соседним группам слиться.

Правила опускания относятся к HTML-элементам, а не к элементам SVG или MathML. Для HTML-потомков в точках интеграции SVG/MathML снова действуют правила HTML. Минимизация предполагает допустимое входное дерево и не исправляет нарушения модели содержимого. Регрессионные тесты сравнивают структуру, пространства имён, атрибуты, текст и комментарии после разбора с выводом, в котором все теги заданы явно.

Пробелы и комментарии

  1. Движок не добавляет между элементами пробелы, отступы и служебные переносы.
  2. Буквальные пробелы из исходника и шаблона сохраняются (внутри текста и атрибутов не вырезаются).
  3. Комментарии HTML (<!-- -->) не выбрасываются и не переформатулируются — выводятся как есть.
  4. Внутри <pre><code> внутренний текст сохраняется как есть, включая завершающий \n.

Особые случаи

  1. Узлы raw выводятся без изменений — без экранирования и без минимизации.
  2. Сериализатор сохраняет встроенный HTML, переданный через raw, в том числе одинарные кавычки. Мост Markdown может переписать пути изображений до сериализации.
  3. Скрипты внутри raw сохраняются дословно; обычный строковый content экранируется.

Частные случаи

  1. Значение d у пути SVG (d="m14.5...c...") содержит последовательности вида c/ — их нельзя разбирать как теги.
  2. data-bem всегда выводится в двойных кавычках.
  3. Символы UTF-8 подставляются напрямую (—, кириллица); сериализатор экранирует текст и значения атрибутов; доверенный raw остаётся без изменений.

Почему не html-minifier

Движок минимизирует типизированное дерево во время сериализации, поэтому отдельный постобработчик готовой строки ему не требуется. Это архитектурный выбор, а не доказательство того, что другие инструменты отклоняют любой HTML с пропущенными необязательными тегами. Подробнее — в решении о модели данных.

Один режим

Отдельного режима разработки с «красивым» HTML нет. В продакшене используется тот же режим: вывод всегда минимальный. Отлаживать страницу удобнее через режим output: 'bemjson' (см. двухпроходный рендеринг), а не через форматированный HTML.