Fest — это шаблонизатор общего назначения, компилирующий XML шаблоны в самодостаточные JavaScript функции. Для установки требуется Node.js >= 0.8.
npm install fest
Шаблоны представляют собой XML документы, содержащие HTML, текстовые данные и управляющие конструкции. Шаблон задается парным элементом <fest:template>
(с указанием пространства имен http://fest.mail.ru
). Например:
<fest:template xmlns:fest="http://fest.mail.ru">
Hello!
</fest:template>
Данные передаваемые в шаблон, доступны через переменную с именем, указанным в атрибуте context_name
элемента fest:template
:
<fest:template xmlns:fest="http://fest.mail.ru" context_name="json">
Hello, <fest:value>json.name</fest:value>!
</fest:template>
Чтобы посмотреть результат работы, приведенных выше шаблонов, необходимо воспользоваться встроенной утилитой fest-render или API библиотеки.
Утилита fest-render
используется для компиляции и последующего запуска скомпилированного шаблона из командной строки. В таком случае, для передачи данных в шаблон используется JSON-файл.
hello.json
{"name": "John"}
hello.xml
<fest:template xmlns:fest="http://fest.mail.ru" context_name="json">
Hello, <fest:value>json.name</fest:value>!
</fest:template>
$ ./node_modules/.bin/fest-render --json=hello.json hello.xml
Hello,John!
Замечание: начальные и конечные пробелы в текстовых узлах удаляются при компиляции. Если необходимо вывести символ пробела, можно вопспользоваться fest:space
.
Функция render()
API компилирует и исполняет шаблон с заданными параметрами.
hello.js
var fest = require('fest');
console.log(fest.render('hello.xml'), {name: 'John'});
$ node ./hello.js
Hello,John!
Служит для вывода значения JavaScript выражения. Поддерживаем 4 режима вывода: html (по умолчанию), text, js и json.
<fest:script><![CDATA[
var value = '"<script/>"';
]]></fest:script>
<fest:value>value</fest:value><!-- "<script/>" -->
<fest:value output="text">value</fest:value><!-- "<script/>" -->
<fest:value output="js">value</fest:value><!-- \"\u003Cscript\/\u003E\" -->
<fest:value output="json">value</fest:value><!-- "\"\u003Cscript/\u003E\"" -->
Устаналивает локальную JavaScript переменную.
<fest:var name="question">Ultimate Question of Life, The Universe, and Everything</fest:var>
<fest:value>question</fest:value><!-- Ultimate Question of Life, The Universe, and Everything -->
<fest:var name="answer" select="question.length - 13" />
<fest:value>answer</fest:value><!-- 42 -->
Выводит неформатированный текст.
<fest:text>"Hello"</fest:text><!-- "Hello" -->
Служит для вывода пробела. Необходим в тех случаях, когда пробел в тектовом узле удаляется при компиляции, например:
Hello,<fest:space/><fest:value>json.name</fest:value>!<!-- Hello, John! -->
Объявляет именованный подшаблон. Содержимое fest:set
не будет выполнено до тех пор, пока не будет вызван блок с таким же имененем с помощью fest:get
.
<fest:set name="name">John</fest:set>
<fest:set name="full_name">
<fest:get name="name"/><fest:space/>F. Kennedy
</fest:set>
Для fest:set
можно использовать атрибут test
. Операция выполнится, если его значение (JavaScript выражение) истинно.
<fest:set name="name" test="false">should not be set</fest:set>
Внутри fest:set
доступен контекст params
, передаваемый через fest:get
.
<fest:set name="line">
Hello,<fest:space/><fest:value>params.username</fest:value>!
</fest:set>
<fest:get name="line">{username: "John"}</fest:get><!-- Hello, John! -->
Выводит содержимое блока, объявленного через fest:set
.
<fest:get name="name"/>
<fest:get name="name">{'some': 'data'}</fest:get>
С помощью fest:param
можно передавать в блок XML-данные.
<fest:get name="page">
<fest:param name="doctype">html</fest:param>
<fest:params>
{
title: json.title
}
</fest:params>
<fest:param name="content">
<article>
<fest:if test="json.title">
<h1><fest:value>json.title</fest:value></h1>
</fest:if>
</article>
</fest:param>
</fest:get>
<fest:set name="page">
<fest:doctype><fest:value>params.doctype</fest:value></fest:doctype>
<title><fest:value>params.title</fest:value></title>
<body>
<fest:value output="text">params.content</fest:value>
</body>
</fest:set>
Внутри атрибута name
можно использовать JavaScript выражения для вычисления имени блока во время выполнения. Значения выражений, заключенных в фигурные скобки, объединяются с примыкающим текстом. Помимо этого, можно использовать атрибут select
.
<fest:script>
var name = 'foo'
</fest:script>
<fest:get select="name"/><!-- foo -->
<fest:set name="foo">foo</fest:set>
<fest:set name="bar">bar</fest:set>
<fest:get name="b{true?'a':''}r"/><!-- bar -->
Выводит HTML элемент с переменным именем.
<fest:element name="div" />
<fest:script>
var variable = 'table';
</fest:script>
<fest:element select="variable">
fest code
</fest:element>
<fest:element name="{variable2}">
fest code
</fest:element>
Результат:
<div></div><table>fest code</table><div>fest code</div>
Добавляет атрибуты к родительскому элементы. Все fest:attribute
должны быть внутри блока fest:attributes
, который должен идти первым внутри элемента.
<a>
<fest:attributes>
<fest:attribute name="href"><fest:value>json.href</fest:value></fest:attribute>
</fest:attributes>
Some link
</a>
Существует быстрый способ вывести значение в атрибут:
<a href="{json.href}">Some link</a>
Имена атрибутов можно вычислять в момент исполнения шаблона:
<div>
<fest:attributes>
<fest:attribute name="data-{json.name}" value="{json.value}" />
</fest:attributes>
</div>
Предоставляет механизм итерации по объекту.
<fest:script>var obj = {"foo": "bar"}</fest:script>
<fest:each iterate="obj" index="i">
<fest:value>i</fest:value>=<fest:value>obj[i]</fest:value><!-- foo=bar -->
</fest:each>
<fest:each iterate="obj" index="i" value="v">
<fest:value>i</fest:value>=<fest:value>v</fest:value><!-- foo=bar -->
</fest:each>
Выполняет итерацию по массиву или числовому ряду.
<fest:script>json.items = ['a', 'b', 'c']</fest:script>
<fest:for iterate="json.items" index="i">
<fest:value>json.items[i]</fest:value><!-- abc -->
</fest:for>
<fest:for iterate="json.items" index="i" value="v">
<fest:value>v</fest:value><!-- abc -->
</fest:for>
<fest:for from="1" to="5" index="i">
<fest:value>i</fest:value><!-- 12345 -->
</fest:for>
Условный оператор.
<fest:if test="true">
It's true!
</fest:if>
Ветвление. Если ни у одного fest:when
условие не выполнено, будет выбрана ветвь fest:otherwise
.
<fest:choose>
<fest:when test="1">
<fest:text>one</fest:text>
</fest:when>
<fest:when test="2">
<fest:text>two</fest:text>
</fest:when>
<fest:otherwise>
<fest:text>More than 2</fest:text>
</fest:otherwise>
</fest:choose>
Служит для вывода блока CDATA.
<script>
<fest:cdata>
<![CDATA[alert ("2" < 3);]]>
</fest:cdata>
</script>
Выводит HTML комментарий.
<fest:comment>comment</fest:comment>
Задает DOCTYPE генерируемой страницы.
<fest:doctype>html</fest:doctype>
Служит для выполнения произвольного JavaScript.
<fest:script>
<![CDATA[
json.script = 2 < 3;
]]>
</fest:script>
Содержимое fest:script
можно загрузить из файла, указав в атрибуте src
путь к нему.
<fest:script src="script.js"/>
Вставляет содержимое другого шаблона с заданным контекстом.
<fest:script>json.list = ['a', 'b', 'c'];</fest:script>
<fest:include context="json.list" src="./include_foreach.xml"/>
Выводит содержимое файла:
<style type="text/css">
<fest:insert src="style.css"/>
<style>
Компиляция с помощью compile():
var fest = require('fest');
var data = {name: 'Jack "The Ripper"'},
template = './templates/basic.xml';
var compiled = fest.compile(template, {beautify: false}),
template = (new Function('return ' + compiled))();
console.log(template(data));
Компиляция с последующей отрисовкой с помощью render():
var fest = require('fest');
var data = {name: 'Jack "The Ripper"'},
template = './templates/basic.xml';
console.log(fest.render(template, data, {beautify: false}));
basic.xml
<?xml version="1.0"?>
<fest:template xmlns:fest="http://fest.mail.ru" context_name="json">
<h1>Hello,<fest:space/><fest:value output="text">json.name</fest:value></h1>
<!-- По умолчанию все значения fest:value экранируются -->
<!--
Необходимо использовать fest:space или
fest:text для явного указания строк с пробелами
-->
</fest:template>
Результат:
<h1>Hello, Jack "The Ripper"</h1>
Данные на вход:
var data = {
people: [
{name: 'John', age: 20},
{name: 'Mary', age: 21},
{name: 'Gary', age: 55}
],
append: '>>'
}
foreach.xml (основной шаблон):
<?xml version="1.0"?>
<fest:template xmlns:fest="http://fest.mail.ru" context_name="json">
<!-- Контекст можно передавать во вложенные шаблоны -->
<fest:include context_name="json" src="./person.xml"/>
<!-- Значением iterate может быть любое js-выражение -->
<fest:for iterate="json.people.reverse()" index="i">
<!-- Передаваемые значения будут доступны в контексте params -->
<fest:get name="person">json.people[i]</fest:get>
</fest:for>
</fest:template>
person.xml:
<?xml version="1.0"?>
<fest:template xmlns:fest="http://fest.mail.ru" context_name="json">
<!--
Используем set для объявления блока,
который используем в родительском шаблоне
-->
<fest:set name="person">
<p>
<fest:script><![CDATA[
var first = params.name[0],
other = params.name.slice(1);
]]></fest:script>
<fest:value>json.append</fest:value>
<strong>
<fest:value>first</fest:value>
</strong>
<fest:value>other</fest:value>
</p>
</fest:set>
</fest:template>
Результат:
<p>>><strong>G</strong>ary</p>
<p>>><strong>M</strong>ary</p>
<p>>><strong>J</strong>ohn</p>
<?xml version="1.0"?>
<fest:template xmlns:fest="http://fest.mail.ru" context_name="json">
<fest:set name="host">http://e.mail.ru</fest:set>
<fest:set name="all">msglist</fest:set>
<fest:set name="new">sentmsg?compose</fest:set>
<fest:set name="all_link">
<fest:get name="host"/>/<fest:get name="all"/>
</fest:set>
<fest:set name="new_link">
<fest:get name="host"/>/<fest:get name="new"/>
</fest:set>
<ul>
<!-- fest:attribute добавляет параметр к родительскому тегу -->
<li><a>
<fest:attributes>
<fest:attribute name="href"><fest:get name="all_link"/></fest:attribute>
</fest:attributes>
Все сообщения
</a></li>
<li><a>
<fest:attributes>
<fest:attribute name="href"><fest:get name="new_link"/></fest:attribute>
</fest:attributes>
Написать письмо
</a></li>
</ul>
</fest:template>
Результат:
<ul>
<li><a href="http://e.mail.ru/msglist">Все сообщения</a></li>
<li><a href="http://e.mail.ru/sentmsg?compose">Написать письмо</a></li>
</ul>
По умолчанию доступна поддержка плюрализации для русского и английского языка. В параметрах fest.compile
можно передать любую другую функцию плюрализации.
<fest:plural select="json.n">один рубль|%s рубля|%s рублей</fest:plural>
Или англоязычный вариант:
<fest:plural select="json.n">one ruble|%s rubles</fest:plural>
Чтобы вывести символ “%” внутри тега fest:plural
используйте “%%”:
<fest:plural select="json.n">…1%%…|…%s%%…|…%s%%…</fest:plural>
Позволяет указать границы фразы для перевода и контекст для снятия многозначности. Например,
<fest:message context="растение">Лук</fest:message>
<fest:message context="оружие">Лук</fest:message>
Для каждого fest:message
, fest:msg
, обычного текста, заключенного между XML тегами (опция auto_message
), или текстового значения некоторых атрибутов компилятор вызывает функцию events.message
(если такая была указана в параметрах). Данный механизм используется в fest-build
утилите для построения оригинального PO-файла.
Пример вызова fest-build
для создания PO-файла:
$ fest-build --dir=fest --po=ru_RU.po --compile.auto_message=true
Пример компиляции локализованных шаблонов:
$ fest-build --dir=fest --translate=en_US.po
Пример компиляции одного шаблона:
$ fest-compile path/to/template.xml
$ fest-compile --out=path/to/compiled.js path/to/template.xml
$ fest-compile --out=path/to/compiled.js --translate=path/to/en_US.po path/to/template.xml
Необходимо установить Grunt:
$ git clone git@github.com:mailru/fest.git
$ cd fest
$ sudo npm install -g grunt-cli
$ npm install
$ grunt
Grunt используется для валидации JS (тестов) и запуска тестов. Перед отправкой пулл-риквеста убедись, что успешно выполнены git rebase master
и grunt
.
Если необходимо пересобрать шаблоны spec/expected, то выполните:
$ ./bin/fest-build --dir=spec/templates --exclude=*error* --compile.beautify=true --out=spec/expected/initial
$ ./bin/fest-build --dir=spec/templates --exclude=*error* --compile.beautify=true --out=spec/expected/translated --translate=spec/templates/en_US.po