1
Rendering
s.zotov edited this page 2026-04-12 20:46:37 +00:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Рендер и постобработка

Сборка PUG-кода

Функция rebuildOutput() конкатенирует блоки:

//Заголовок                    ← комментарий с именем блока
+spacerLine(40)                ← спейсер (если addSpacing)
tr
  td.paddingWrapper
    span.font.h2 ТЕКСТ

//БАННЕР
+spacerLine(20)
tr
  td
    a(href="https://...")
      img(src="https://...")

Если сертификат включён (certificateEnabled), блоки сертификата добавляются в конец.

Render Cache

  • Ключ: MD5 от slug + pug + gender + genderPaths
  • Хранение: Map в памяти + render-cache.json на диске
  • Размер: LRU, максимум 30 записей
  • Инвалидация: при изменении любого параметра рендера

Preheader

Формат

{текст прехедера} <vk-snippet-end>&#10240;×130
  • Текст прехедера — основное содержимое
  • <vk-snippet-end> — тег для Mail.ru (snippet boundary)
  • &#10240;×130 — невидимые символы-пустышки, чтобы почтовые клиенты не показывали текст письма после прехедера

Цепочка обработки

  1. В Pug: +preheader("текст") (миксин из mixins.pug)
  2. Миксин генерирует #MAILRU_PREHEADER_TAG# в HTML
  3. server.js заменяет #MAILRU_PREHEADER_TAG#<vk-snippet-end/>

Санитизация preheader (защита от инъекций)

preheader.replace(/[\r\n`\\]/g, '').replace(/"/g, '\\"')

Убираются переносы строк (предотвращает выход из строки +preheader("...")), backtick и backslash.

Nowrap (висячие предлоги)

Функция applyNowrap(html) — оборачивает короткие предлоги/союзы с следующим словом в <span style="white-space:nowrap">.

Алгоритм

  1. Находит только <span> элементы с классом h3, за которыми </span></td>
  2. Для каждого совпадения вызывает wrapShort(text):
    • Regex: (?<![a-zA-Zа-яА-ЯёЁ])([a-zA-Zа-яА-ЯёЁ]{1,3})(?:\s|&nbsp;)+(\S+)
    • Слово 1-3 буквы + пробел + следующее слово
  3. Placeholder-подход: сначала вставляет \u200B (zero-width space), затем заменяет на <span style="white-space:nowrap">...\u00A0...</span>
  4. Placeholder нужен чтобы не было двойной обработки

Пример

Идеи на каждый день → Идеи <span style="white-space:nowrap">на каждый</span> день

Mindbox Tag Processing

Функция processMindboxTags(html, feedUrl):

Что обрабатывает

  1. Рекомендации — удаляет @{for...}@{end for} блоки
  2. Переменные — резолвит @{set var = value}
  3. Условия:
    • @{if ...DiscountPercent > 0} — скрывает блок если нет скидки
    • @{if ...OldPrice > ...Price} — скрывает если нет реальной скидки
    • Вложенные условия поддерживаются
  4. Подстановки:
    • ${Products...GetByValue('name')} → название товара из фида
    • ${formatDecimal(price)} → форматированная цена с пробелами
    • ${ResizeImage(url, width, height)} → URL картинки
  5. Бейджи — если товар не в наличии, на картинку накладывается полупрозрачный оверлей "Нет в наличии"

Маппинг свойств Mindbox → Фид

Mindbox Фид
name name
vendorName vendor
url url
pictureUrl image
price price
oldPrice oldPrice
description description
customField.vendorCode vendorCode
customField.discountPercent discountPercent
и другие...

Concurrency Limit

Максимум 3 параллельных рендера (MAX_CONCURRENT_RENDERS).

При превышении — HTTP 429 "Слишком много параллельных рендеров, подождите".

Счётчик activeRenders обёрнут в try/finally для гарантии декремента.

Preview в iframe

<iframe sandbox="allow-same-origin" srcdoc={previewHtml} />
  • sandbox="allow-same-origin" — блокирует выполнение скриптов (XSS), но позволяет доступ к DOM
  • fitPreviewFrame() — масштабирует содержимое под viewport
  • Zoom: 40-100%, сохраняется в профиле на сервере
  • Click detection: через contentDocument.addEventListener (не инъекция скрипта)

Quick Edit

Клик по элементу в превью → находит соответствующий блок и поле → показывает плавающий редактор.

  1. injectPreviewClickDetection() — навешивает click listener на iframe DOM
  2. handlePreviewElementClick({text, rect}) — ищет блок по тексту (normalizeMatchText)
  3. openQuickEdit(block, field, iframeRect) — позиционирует редактор у элемента
  4. Изменения сразу применяются к блоку, помечают превью как stale