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

Блок: устройство

Блок описывается файлом .nu.ts. Внутри — регистрация шаблонов через синтаксис nu (block()(...)). Рендеринг состоит из двух проходов: все режимы — и структурные, и отображения в HTML — в первом (renderToBemJson), второй (renderToHtml) только сериализует результат. Подробно о режимах — в разделе шаблоны nu.

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

Один файл blocks/<name>/<name>.nu.ts описывает один блок:

import { block } from 'nuckty';

interface ButtonProps {
    type?: 'button' | 'submit';
    size?: 's' | 'm' | 'l';
}

export default function templates(): void {
    // Регистрация блока: движок загружает .nu.ts по зависимостям и вызывает templates
    block<ButtonProps>('button')({
        tag: 'button',
        attrs: (n) => ({ type: n.props?.type ?? 'button' }),
        mods: (n) => ({ size: n.props?.size ?? 'm' }),
    });

    // Элементы и модификаторы — отдельными декларациями
    block('button').elem('icon')({ tag: 'span' });
}

Файл экспортирует default function templates() — точку входа. Движок вызывает её при загрузке блока. Шаблон регистрируют через block(...) — он привязан к коллекции шаблонов текущей сборки.

Режимы

Весь блок описан в одном файле. Все режимы — и структурные, и режимы отображения — движок применяет на одном проходе над деревом (renderToBemJson); второй проход (renderToHtml) только сериализует результат.

Структурные режимы меняют дерево:

// content: задать содержимое
block<{ summary?: string }>('card').elem('body')({
    content: (n) => n.props?.summary ?? '',
});

// replace: заменить блок
block<{ color?: string }>('badge').replace((n) => ({
    block: 'tag', mods: { color: n.props?.color ?? 'default' },
}));

// wrap: обернуть
block('root').wrap((n) => ({
    tag: 'html',
    content: [{ tag: 'head' }, { tag: 'body', content: n }],
}));

Режимы отображения задают, каким узел выйдет в HTML:

// tag, attrs, cls, mods, elemMods, mix, js, bem
block('menu')({ tag: 'ul' });
block('menu').elem('item')({ tag: 'li' });
block<{ url?: string; id?: string }>('link')({
    attrs: (n) => ({ href: n.props?.url ?? '/', 'data-track': n.props?.id ?? '' }),
});

Минимизация разметки — не режим. На втором проходе движок минимизирует готовую разметку. Подробнее — HTML на выходе.

Сопутствующие ассеты

Рядом с .nu.ts могут лежать CSS и JS блока:

blocks/button/
  button.nu.ts
  button.css
  button.js

CSS и JS включаются по записи блока в декларации бандла. Использование блока в контенте само по себе не добавляет его ассеты. Подробнее — зависимости блоков.

Специфичность и переопределение

Декларации в одной коллекции ранжируются по специфичности: block < elem < mod < elemMod < when. При равной специфичности выбирается последняя. Например, внутри одной функции регистрации можно дополнить ранее заданные модификаторы:

// после базовой декларации в той же функции templates():
block('button')({ mods: (_n, next) => ({ ...next(), size: 'l' }) });  // всегда 'l'

При переопределении через уровни загружается только последний файл .nu.ts блока. Чтобы сохранить базовые декларации, явно импортируйте и вызовите их функцию регистрации внутри своего templates(), прежде чем добавить собственные. Иначе новый файл должен описывать всё необходимое поведение.

Чего в файле нет

  • Нет ручной регистрации каждого блока. Движок находит .nu.ts по зависимостям, загружает файл и вызывает templates. Подробнее — зависимости.
  • Нет импорта CSS и JS внутри .nu.ts. Ассеты лежат рядом, и бандлер находит и добавляет их в сборку по зависимостям.
  • Нет вызова applyNext(). Вместо отдельного метода — параметр next в функции режима: (n, next) => ....