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}"/>