Форум

Методологія

Інструментарій

Платформа

Спільнота

Перевод этой статьи на ваш язык отсутствует, вы можете помочь нам перевести.

API

Выбор движка, компиляция и применение шаблонов

Движок BEMHTML

var bemxjst = require('bem-xjst');
var bemhtml = bemxjst.bemhtml;

// Добавляем шаблон
var templates = bemhtml.compile(function() {
    block('quote').tag()('q');
});

// Добавляем данные
var bemjson = { block: 'quote', content: 'Пришел, увидел, отшаблонизировал.' };

// Применяем шаблоны
var html = templates.apply(bemjson);

В результате html будет содержать строку:

<q class="quote">Пришел, увидел, отшаблонизировал.</q>

Движок BEMTREE

var bemxjst = require('bem-xjst');
var bemtree = bemxjst.bemtree;

// Добавляем шаблон
var templates = bemtree.compile(function() {
    block('phone').content()({ mask: '8-800-×××-××-××', mandatory: true });

    block('page').content()([
        { block: 'header' },
        { block: 'body' },
        { block: 'footer' }
    ]);
});

// Добавляем данные
var bemjson = [ { block: 'phone' }, { block: 'page' } ];

// Применяем шаблоны
var result = templates.apply(bemjson);
// result будет содержать:
[
    {
        block: 'phone',
        content: {
            mask: '8-800-×××-××-××',
            mandatory: true
        }
    },
    {
        block: 'page',
        content: [
            { block: 'header' },
            { block: 'body' },
            { block: 'footer' }
        ]
    }
]

Добавление шаблонов

Чтобы добавить шаблоны к экземпляру templates воспользуйтесь методом compile.

var bemxjst = require('bem-xjst');

// Создаём экземпляр класса templates
var templates = bemxjst.bemhtml.compile(function() {
    block('header').tag()('h1');
});

// Добавляем данные
var bemjson = { block: 'header', content: 'Документация' };

var html = templates.apply(bemjson);
// html: '<h1 class="header">Документация</h1>'

// Добавляем шаблоны к уже созданному экземпляру класса templates
templates.compile(function() {
    block('header').tag()('h2');
});

html = templates.apply(bemjson);
// Теперь HTML-тег стал h2 вместо h1:
// html: '<h2 class="header">Документация</h2>'

Если вам нужно собрать все шаблоны в бандл, то эффективнее использовать метод generate.

Настройки

Разделители в именовании БЭМ-сущностей

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // В этом примере мы не добавляем пользовательских шаблонов.
    // Для рендеринга HTML будет использовано поведение шаблонизатора по умолчанию.
    }, {
        // Настройка БЭМ-нейминга
        naming: {
            elem: '__',
            mod: '_'
        }
    });

var bemjson = {
    block: 'page',
    mods: { theme: 'gray' },
    content: {
        elem: 'head',
        elemMods: { type: 'short' }
    }
};

var html = templates.apply(bemjson);

В результате html будет содержать строку:

<div class="page page_theme_gray">
    <div class="page__head page__head_type_short"></div>
</div>

Подробнее читай в cоглашении по именованию.

Поддержка JS-экземпляров для элементов (bem-core v4+)

В библиотеке bem-core начиная с 4 версии появилась поддержка JS-экземпляров для элементов.

Это требует добавления класса i-bem для элементов, имеющих JS-параметры.

Добиться этого можно с помощью опции elemJsInstances:

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // В этом примере мы не добавляем пользовательских шаблонов.
    // Для рендеринга HTML будет использовано поведение шаблонизатора по умолчанию.
    }, {
        // Включаем поддержку JS-экземпляров для элементов
        elemJsInstances: true
    });

var bemjson = {
    block: 'b',
    elem: 'e',
    js: true
};

var html = templates.apply(bemjson);

В результате html будет содержать строку:

<div class="b__e i-bem" data-bem='{"b__e":{}}'></div>

Закрытие одиночных элементов

Если у вас нет необходимости генерировать корректный XHTML (закрывать одиночные элементы), можно немного сэкономить, отключив опцию xhtml:

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // В этом примере мы не добавляем пользовательских шаблонов.
    // Для рендеринга HTML будет использовано поведение шаблонизатора по умолчанию.
    }, {
        // Выключаем режим XHTML
        xhtml: false
    });

var bemjson = {
    tag: 'br'
};

var html = templates.apply(bemjson);

В результате html будет содержать строку:

<br>

Опциональные закрывающие теги

При помощи опции omitOptionalEndTags шаблонизатор не будет выводить опциональные закрывающие теги. По умолчанию эта опция выключена.

Список опциональных закрывающих тегов можно найти в спецификациях HTML4 и HTML5.

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // В этом примере мы не добавляем пользовательских шаблонов.
    // Для рендеринга HTML будет использовано поведение шаблонизатора по умолчанию.
    }, {
        // Отключаем вывод опциональных закрывающих тегов
        omitOptionalEndTags: true
    });

var bemjson = {
    tag: 'table',
    content: {
        tag: 'tr',
        content: [
            { tag: 'th', content: 'table header' },
            { tag: 'td', content: 'table cell' }
        ]
    }
};

var html = templates.apply(bemjson);

В результате html будет содержать строку:

<table><tr><th>table header<td>table cell</table>

Атрибуты без кавычек

Спецификация HTML позволяет опустить необязательные кавычки у атрибутов, которые не содержат пробелов и прочих специальных символов. Подробности читайте в спецификациях HTML4 и HTML5.

С помощью опции unquotedAttrs вы можете включить такое поведение в рендеринге BEMHTML.

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // В этом примере мы не добавляем пользовательских шаблонов.
    // Для рендеринга HTML будет использовано поведение шаблонизатора по умолчанию.
    }, {
        // Разрешаем пропускать кавычки в атрибутах если это возможно:
        unquotedAttrs: true
    });

var bemjson = { block: 'b', attrs: { name: 'test' } };

var html = templates.apply(bemjson);

В результате html будет содержать строку:

<div class=b name=test></div>

Экранирование

Вы можете включить экранирование содержимого поля content опцией escapeContent. В этом случае ко всем строковым значениям content будет применена функция xmlEscape.

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // В этом примере мы не добавляем пользовательских шаблонов.
    // Для рендеринга HTML будет использовано поведение шаблонизатора по умолчанию.
    }, {
        escapeContent: true
    });

var bemjson = {
    block: 'danger',
    // Потенциально опасный и неконтролируемый текст
    content: '&nbsp;<script src="alert()"></script>'
};

var html = templates.apply(bemjson);

В результате html будет содержать строку:

<div class="danger">&amp;nbsp;&lt;script src="alert()"&gt;&lt;/script&gt;</div>

Если вам нужно вывести строку без экранирования воспользуйтесь специальным значением поля content: { html: '…' }.

Пример

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile(function() {
    // В этом примере мы не добавляем пользовательских шаблонов.
    // Для рендеринга HTML будет использовано поведение шаблонизатора по умолчанию.
    }, {
        escapeContent: true
    });

var bemjson = {
    block: 'trusted',
    // Безопасное содержимое
    content: 'I <3 you!'
};

var html = templates.apply(bemjson);

В результате html будет содержать строку:

<div class="trusted">I <3 you!</div>

Обратите внимание, что в content.html ожидается именно строка.

Runtime проверки ошибок в шаблонах и входных данных

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

var bemxjst = require('bem-xjst');
var bemhtml = bemxjst.bemhtml;

var templates = bemhtml.compile(function() {
  block('b').content()('yay');

  block('mods-changes').def()(function() {
    this.ctx.mods.one = 2;
    return applyNext();
  });
}, { runtimeLint: true });

var html = templates.apply([
  { block: 'b' },

  // boolean attributes
  { block: 'b', attrs: { one: true, two: 'true' } },

  // mods for elem
  { block: 'c', elem: 'e', mods: { test: 'opa' } },

  // Присвоения в this.ctx.mods
  { block: 'mods-changes', mods: { one: '1', two: '2' } }
]);

В результате выполнения этого кода в STDERR будут записаны предупреждения:

BEM-XJST WARNING: boolean attribute value: true in BEMJSON: { block: 'b', attrs: { one: true, two: 'true' } }

Notice what bem-xjst behaviour changed: https://github.com/bem/bem-xjst/releases/tag/v4.3.3

BEM-XJST WARNING: mods for elem in BEMJSON: { block: 'c', elem: 'e', mods: { test: 'opa' } }

Notice what bem-xjst behaviour changed: https://github.com/bem/bem-xjst/releases/tag/v5.0.0

BEM-XJST WARNING: looks like someone changed ctx.mods in BEMJSON: { block: 'mods-changes', mods: { one: 2, two: '2' } } old value of ctx.mod.one was 1

Notice that you should change this.mods instead of this.ctx.mods in templates

Режим production

Вы можете использовать опцию production, чтобы отрендерить весь BEMJSON, даже если в одном из шаблонов произошла ошибка.

Пример

var template = bemxjst.compile(function() {
  block('b1').attrs()(function() {
    var attrs = applyNext();
    attrs.undef.foo = 'bar';
    return attrs;
  });
}, { production: true });
var html = template.apply({ block: 'page', content: { block: 'b1' } });

html будет содержать <div class="page"></div>.

Если в результате выполнения шаблонов случится ошибка, то узел не будет отрендерен, но шаблонизатор продолжит работу, выведя в STDERR сообщение об этой ошибке.

$node index.js 1> stdout.txt 2> stderr.txt

$ cat stdout.txt
<div class="page"></div>

$ cat stderr.txt
BEMXJST ERROR: cannot render block b1, elem undefined, mods {}, elemMods {} [TypeError: Cannot read property 'undef' of undefined]

Настройка exportName

Вы можете использовать опцию exportName чтобы выбрать имя переменной, в которой будет храниться движок. По умолчанию движки хранятся в BEMHTML и BEMTREE.

За примером обратитесь к следующему разделу, про создание бандла.

Подключение сторонних библиотек

Технологии BEMTREE и BEMHTML поддерживают возможность подключения сторонних библиотек как глобально, так и для разных модульных систем с помощью опции requires.

Для подключения укажите название библиотеки и в зависимости от используемой модульной системы:

  • имя глобальной переменной;
  • имя модуля из YModules;
  • путь к модулю для CommonJS.
{
    requires: {
        'lib-name': {
            globals: 'libName',           // Название переменной в глобальной видимости
            ym: 'lib-name',               // Имя модуля из YModules
            commonJS: 'path/to/lib-name'  // Путь к модулю CommonJS относительно собираемого файла
        }
    }
}

В шаблонах модули будут доступны с помощью метода this.require, например:

block('button').content()(function () {
    var lib = this.require('lib-name');

    return lib.hello();
});

Не обязательно указывать все модульные системы для подключения библиотеки.

Например, можно указать зависимости глобально. В этом случае модуль всегда будет передаваться из глобальной переменной, даже если в среде исполнения будет модульная система.

{
    requires: {
        'lib-name': {
            globals: 'dependName' // Название переменной в глобальной видимости
        }
    }
}

Пример подключения библиотеки moment

Указывается путь к модулю:

{
    requires: {
        moment: {
            commonJS: 'moment',  // Путь к модулю CommonJS относительно собираемого файла
        }
    }
}

В шаблонах модуль будет доступен с помощью метода this.require('moment'). Код шаблона пишется один раз, одинаково для исполнения в браузере и в Node.js:

block('post').elem('data').content()(function() {
    var moment = this.require('moment');  // Библиотека `moment`

    return moment(this.ctx.date) // Время в ms, полученное с сервера
        .format('YYYY-MM-DD HH:mm:ss');
});

Расширение BEMContext

Вы можете расширять BEMContext, чтобы использовать в теле шаблона пользовательские функции.

var bemxjst = require('bem-xjst');
var templates = bemxjst.bemhtml.compile('');

// Расширяем прототип контекста
templates.BEMContext.prototype.hi = function(name) {
    return 'Hello, ' + name;
};

// Добавляем шаблоны
templates.compile(function() {
    block('b').content()(function() {
        return this.hi('templates');
    });
});

// Входные данные
var bemjson = { block: 'b' };

// Применяем шаблоны
var html = templates.apply(bemjson);

В результате html будет содержать строку:

<div class="b">Hello, templates</div>

Создание бандла

Метод generate генерирует JS-код, который может быть передан и выполнен в браузере для получения объекта templates.

var bemxjst = require('bem-xjst');
var bemhtml = bemxjst.bemhtml;

var fs = require('fs');

var bundle = bemhtml.generate(function() {
    // пользовательские шаблоны
    // ...

}, { exportName: 'bemhtml8x' });

// Записываем бандл на файловую систему
fs.writeFile('bundle.js', bundle, function(err) {
  if (err) return console.error(err);
  console.log('Done');
});

В результате вы получите файл bundle.js, в котором будет лежать код движка и пользовательские шаблоны. Код движка будет доступен в переменной bemhtml8x и полностью готов к исполению в браузерах, node.js или любой виртуальной машине JS.

Читать далее: входные данные

Якщо ви помітили помилку, або хочете доповнити статтю, ви завжди можете або написати нам про це на Гітхабі, або поправити статтю з допомогою prose.io.