Live coding & components
| Feast | Fest | |
|---|---|---|
| Компиляция | runtime | server/proxy |
| Компоненты | есть | нет |
| Поддержка BEM | есть | нет |
| Расширяемость | максимальная | нет (только PR) |
| Синтаксис | любой | fat-xml |
| Рендер | VDOM | string |
| и так далее... |
// Прасим шаблон в подобие DOM дерева
var template = feast.parse("<img src=\"{attrs.src}\"/>");
#!+ // Создаем фнукцию генерации VDOM
#!- var compiledTemplate = feast.compile(template);
#!+ // Получаем фрагмент VDOM
var fragment = compiledTemplate({src: "foo.png"});
#!- // fragment: {tag: "img", attrs: {src: "foo.png"}}
#!+
// Присоединяем фрагмент-vdom к body
#!- var virtualNode = feast.vdom.append(document.body, fragment);
| Feast | Fest | |
|---|---|---|
| Настройка сервера (proxy/watch) | не требуется | обязательна |
| Размер итогового шаблона | неизменен, либо меньше | увеличивается |
| Работа в браузере | да | нет |
#!+ <a> #!+ <fest:attributes> #!+ <fest:if test="params.href"> #!+ <fest:attribute name="href"> #! <fest:value>params.href</fest:value> #!- </fest:attribute> #!- </fest:if> #!- </fest:attributes> #! <span>link</span> #!- </a>
#!+ <a>
#! <fn:attr
#! name="href"
#! value="{attrs.href}"
#! test="attrs.href"/>
#! <span>link</span>
#!- </a>
<!-- Комментарий к верстке -->
<a bem:block="btn">
<fn:attr
name="href"
value="#!{attrs.href}"/>
<fn:if test="attrs.icon">
<b:icon name="{attrs.icon}"/>
</fn:if>
<span bem:elem="text">link</span>
</a>
#! // Комментарий к верстке
#!+ a.btn {
#! href: "#!" + attrs.href
#!+ if attrs.icon {
#! &icon { name: attrs.icon }
#!- }
#! span.&__text | link
#!- }
// Определяем блок
&ctrl = a.ctrl | {{attrs.name}}
table **>** tr {
td {
input[type="checkbox"] {
id: "cbx"
on-change: attrs.handleChecked(evt)
checked: attrs.state
}
label[for="cbx"] | check me
}
td > &ctrl { name: "edit" } **+** &ctrl { name: "remove" }
}
Это большая отдельная тема, о который можно поговорить отдельно, если будет интерес.
Основная проблема не прасинге, а именно описательной возможности синтаксиса.
Как я уже говорил, fest можно расширить только через Pull Request, что согласитесь не очень удобно.
А feast?
<!-- Выведем время -->
<fn:value>params.time</fn:value>
#!+
<!-- Форматируем -->
#!- <fn:value>moment(params.time).fromNow()</fn:value>
#!+
<!-- Эээ, а как пробросить moment? -->
<!-- Так? -->
#!- <fn:value>require("momentjs")(params.time).fromNow()</fn:value>
import feast from "feast";
import moment from "momentjs";
#!+ // Определяем модификатор
feast.mods["time:fromNow"] = function (value) {
#! return moment(value).fromNow();
#!- };
#!+
<!-- Используем -->
#!- <fn:value **mod="time:fromNow"**>params.time</fn:value>
<my:loop from="1" to="{attrs.max}" as="val" test="attrs.enabled">
<fn:value>val</fn:value>
</my:loop>
import feast from "feast";
// Определяем тег с namespace (можно и без него)
feast.tags["my:loop"] = {
#! scope: true, // означает, что тег выводу не подлежит
#! required: ["from", "to"], // обязательные атрибуты
#! expressions: ["test"], // javascript выражения
#!+ toCode() {
#!+ return [
#! "if ($test) for (var &as = $from; &as <= $to; &as++) {",
#! "}"
#!- ];
#!- }
};
// А что, если я хочу использовать namespace? <use:foo/> <use:bar/> <use:baz/>
import feast from "feast";
// Совпадение на любой тег в определенном namespace
feast.tags["use:*"] = {
scope: true,
toCode(attrs, node) {
const [ns, name] = node.name.split(":");
// ...
}
};
Приоритеты поиска совпадений
feast.tags["ns:tag-name"] = {}; // максимальный приоритет
feast.tags["ns:*"] = {}; // средний
feast.tags["*:tag-name"] = {}; // минимальный
В первую очередь, feast — это инструмент, который помогает быстро и гибко разрабатывать и поддерживать блоки проекта, а не очередной шаблонизатор и тем более framework.
cd ./feast/ npm start > feast@0.10.0 start /Users/RubaXa/Dropbox/git/feast > node feast --playground=./blocks/ Feast running at http://127.0.0.1:2015/
./blocks/ ./checkbox/ checkbox.html // шаблон checkbox.js // js-реализация checkbox.scss // стили checkbox.spec.js // спецификация
<div> <!-- содержимое блока (если честно, этого комментария нет) --> </div>
.checkbox {
padding: 10px;
background: red;
}
Сейчас я покажу, как происходит верстка и тестирование верстки.
export default {
cases: {
"base": {attrs: {}},
"checked": {
attrs: {
checked: true,
$tests: [ // тесты
{
msg: "выделен",
mods: ["checked"]
},
{trigger: "click"},
{
msg: "невыделен",
mods: []
}]
}
}
}
};
<div on-click="_this.set('checked', !attrs.checked)">
<bem:mod name="checked" test="attrs.checked"/>
<div bem:elem="checkmark"></div>
</div>
on-click — отстой, фу так писать!
<div **remit:click="toggle"**> <bem:mod name="checked" test="attrs.checked"/> <div bem:elem="checkmark"></div> </div>
import feast from "feast";
import template from "feast-tpl!./checkbox.html";
/** @class UICheckbox */
export default feast.Block.extend(/** UICheckbox# */{
#! name: "checkbox", // название блока
#! template: template, // шаблон
#!+ events: { // обработка событий (не DOM)
#! "toggle": "handleToggle"
#!- },
#!+ handleToggle() {
this.set("checked", !this.attrs.checked);
#!- }
});
<!-- Было: -->
<fest:get name="checkbox">{checked: true}</fest:get>
<fest:get name="checkbox">{checked: attrs.state}</fest:get>
<!-- Стало: -->
<b:checkbox checked/>
<b:checkbox checked="{attrs.state}"/>