fest
fest copied to clipboard
Работа с BEM
Друзья, есть серьёзный вопрос, по поводу шаблонов и BEM/toolkit.
Чем дольше я их пишу, тем больше мне всё это не нравиться. Слишком избыточно, даже читать шаблон тяжело. Вот пример простого checkbox:
https://gist.github.com/RubaXa/5077273#file-cbx-fest-xml
И это достаточно простой пример, что уж говорить про другие компоненты, такие как b-datalist. Работать с эти просто не возможно, открывая такой шаблон, приходиться минут 5 вникать. Самое смешное, что 5 минут, это если ты сам и написал. Рефакторить ещё сложней.
Вместо этого месива, предлагаю сделать свой синтаксис, для работы с BEM терминологией, например такой:
https://gist.github.com/RubaXa/5077273#file-cbx-bem-xml https://gist.github.com/RubaXa/5077273#file-b-datalist-bem-xml
По мне так этот код понятен и лаконичен, но самое главное, это будут уже не set. Компиляроватся всё будет в единую функию, которая описывает весь блок, со всеми его элементами. Сейчас это у нас set с множеством get'ов, что тормозит.
Что думаете?
Все зависит от вариантов использования https://gist.github.com/RubaXa/5077273#comment-790093 С другой стороны, я за поддержку препроцессора или сахара для феста при работе БЭМ. Готов участвовать в обсуждении.
Upd: https://gist.github.com/RubaXa/5077273#comment-790105
https://gist.github.com/RubaXa/5077273#comment-790109
Давайте тут общаться, чтобы не распылять внимание.
В общем, чтобы не ходить вокруг да около, я хочу и готов сам сделать, подобный синтаксис:
&bem b-datalist {
ctx.head ? &__head ctx.head
for item in ctx.items {
&__item item
}
ctx.foot ? &__foot ctx.foot
}
&bem b-datalist__item {
cols = $block.getCols();
mods: {
"first": $first && !$parent.head
"last": $last
"selected": $parent.selected[ctx.id]
}
cols.cbx ? &__cbx {
&cbx { checked: ctx.selected }
}
cols.pic ? &__pic {
style: "background-image: url('{$block.getItemPic(ctx)}');"
}
}
Он только для работы с BEM, есть возможность работы с $parent контекстом. Но самое главное, это код будет мега оптимизирован, никаких " b-name_" + mods.join(" b-name_")
.
Готов рассматривать альтернативу, но как по мне, лаконичнее этого, только телепатия.
Я не буду тебе мешать :-) Мне нравится.
Я тебя правильно понимаю, что &bem ... будет трансформирован в fest:script
?
И как будет происходить вызов блока в шаблоне?
Действительная реальность состоит в том, что params.mods
не пригоден для реальной разработки, рассмотрим пример:
<fest:set name="b-input">
<div class="b-input{ params.mods ? ' b-input_' + params.mods.join(' b-input_') : '' }">
<fest:attributes>
<fest:attribute test="Array.indexOf(params.mods, 'disabled') != -1" name="aria-disabled">disabled</fest:attribute>
</fest:attributes>
<input type="text" class="b-input__el" value="{params.value}">
<fest:attributes>
<fest:attribute test="Array.indexOf(params.mods, 'disabled') != -1" name="disabled"/>
<fest:attribute test="Array.indexOf(params.mods, 'readonly') != -1" name="readonly"/>
</fest:attributes>
</input>
</div>
</fest:set>
Ну как, читабельно? Быстро ли будет работать такой код? Нет и нет!
Но как бы было красиво, если бы оно выглядело подобным образом:
&bem b-input {
mods: {
"disabled": ctx.disabled
"readonly": ctx.readonly
}
&__el {
tag: "input"
attrs: {
type: "text"
value: $block.value
readonly: $block.mods.readonly
}
disabled: $block.mods.disabled
}
}
В шаблоне вызов будет примерно так:
<fest:template>
&b-input {
value: json.username
}
</fest:template>
Блок будет компилироваться в единую функцию, даже если состоит из многих элементов.
Ошибки при компиляции и т.п. всё будет, это не проблема. Конечно цельной картины у меня ещё нет, есть наброски, как и что компилировать, в какую примерно функцию всё это превращать. Сейчас главное понять, всех ли это устраивает. Моё мнение думаю понятно :]
На счет быстро – покажут только тесты ;-) Выглядит круто, мне нравится что через entities реализовано. Переопределение блоков будет?
Если сделаешь тесты на производительность, то давай.
А если не сделает?
Если сделаешь тесты на производительность, то давай.
Андрей, прежде чем делать, нужно понять, как видят другие это, может только я ною, а всем остальным хорошо. Важно мнение всех, кто сейчас использует fest.
Хотелось бы еще поддержку такого:
<fest:template>
&b-page {
title: json.title,
head: (
&b-page__style { href: '....' }
),
html: (
&b-header json.header
&b-footer json.footer
)
}
</fest:template>
Нужно думать, но это уже сложней.
Хотелось бы чтобы препроцессор был ориентирован на работу с HTML-сущностями. Добавления классов и id я вижу так:
div#id.foo
if true
&.bar
else
&.baz
<div id="id" class="foo bar" />
Без & результат трансляции будет как в Jade:
<div id="id" class="foo">
<div class="bar" />
</div>
Саш, в том то и дело, я не собираюсь делать Jade, мне это не нужно, это никак не поможет в решении той задачи, про которую я говорю. Я хочу получить инструмент для создания BEM компонентов.
Jade, который создаёт БЭМ-компоненты
Объявление
@block bem_block_input(value):disabled:readonly // псевдо-классы == модификаторы
.b-input // Тэг по-умолчанию - div
input.&__el( // & работает относительно блока
value=value
, disabled=matches(:disabled) // Вместо matches и псевдо-класса, возможна другая функция
, readonly=matches(:readonly) // например mod(disabled)
)
#{__context__}
@export bem_block_input as b-input
Использование
@import b-input // Компоненты могут импортироваться по-умолчанию, поэтому эта строчка не обязательна
+b-input("Значение инпута"):disabled // :disabled - выставляет модификатор
span.hint Подсказка, которая будет вставлена в __context__
Пример со страницей:
@block page(title)
html
head
title #{title}
body.b-page
div.&__body=body
div.&__footer
=footer //div внутри b-page__footer
@export page as b-page
Использование
@import b-page
+b-page("Заголовок страницы")
=body
div
span Контент страницы
=footer
footer
div Футер страницы
Дополнение к ответу Егора:
# инлайновый блок
@set __block1(title)
....
@set block2(title)
@get __block1
....
@set block3(title)
@get __block1
....
@export block1 as b-page1, block2 as b-page2
Во, Егор дело говорит, не в смысле что мне все нравиться, а в смысле он понимает меня :] Но, по мне так это вырви глаз, но это моё никчемное мнение.
Егор, вот покажи, как у тебя будет выглядить такой блок:
Описание:
&bem b-dropdown {
mods: {
"disabled": ctx.disabled
"expanded": ctx.expanded
}
data: {
group: ctx.group
mnemo: ctx.mnemo || ctx.group
}
for item in ctx.items {
if item == 'hr'
&__hr
else
&__item item
}
}
&bem b-dropdown__item {
mods: {
"active": ctx.id == $block.actveId
"disabled": ctx.disabled
}
data: {
"id": item.id
"name": item.name
}
context: ctx.text
}
Вызов
&b-dropdown {
name: 'markAs'
items: [
{ name: '...', text: '...' },
'hr',
{ etc }
]
}
Как-то так. Для читаемости, можно предусмотреть пробелы и переносы строк в модификаторах, но это уже не по jade-синтаксису
@mixin dropdown_block_item(name, id):active=&.actveId:disabled //:active=&.actveId - Значение по-умолчанию и & - ссылка на родительский блок
.&__item(data-id=id, data-name=name)
@yield // Вывод контента, тоже самое, что и #{__context__}
@block dropdown_block( group, items
, mnemo=group // Значение по-умолчанию
):disabled:expanded
.b-dropdown(data-group=group, data-mnemo=mnemo)
@each item in items
@if item == "hr"
.&__hr
@else
+dropdown_block_item(item.name, item.id):item... // :<var>... - означает "берём модификаторы"
@export dropdown_block as bem.b-dropdown
Вызов:
@import bem.b-dropdown as b-dropdown
@var items = [
{ name: '...', text: '...' },
'hr',
{ etc }
]
+b-dropdown('markAs', items)
Егор, я конечно всё понимаю, но просто взгляни: http://www.rubaxa.org/screenshot/add7ccaeca04b744d4f71a39a542.png
И главное, твой пример не тоже самое, что у меня.data-group
, модификатор active
, всё не так.
Ну это просто пример синтаксиса. Реализацию можно сделать чего-угодно. Хотя, я тоже понимаю, что выходит несколько громоздко. Но, зато, это почти jade
Jade решает свою задачу, у нас она иная.
Костин вариант выглядит проще.
Если сделать, так что &bem b-page
будут скомпилированы в обычный сет-блок (тебе по сути без-разницы, это обычная функция от параметров ctx и __fest_context), то будет возможность вызвать p-page и передавать в него XML данные.
Как вариант:
@set block (data) {
div
@xml::attr name as name:
name = | data.disabled => \foo
| otherwise => \bar
@xml::attr class as class:
@if data.foo:
class = \foo
@else:
class = \bar
class += \ baz
}
@get block (data)
Пытался решить такую задачу:
<fest:template>
<fest:set name="block">
<div>
<fest:attributes>
<fest:attribute name="name">
<fest:choose>
<fest:when test="data.disabled">
<fest:text>foo</fest:text>
</fest:when>
<fest:otherwise>
<fest:text>bar</fest:text>
</fest:otherwise>
</fest:choose>
</fest:attribute>
<fest:attribute name="name">
<fest:choose>
<fest:when test="data.foo">
<fest:text>foo</fest:text>
</fest:when>
<fest:otherwise>
<fest:space />
<fest:text>bar</fest:text>
</fest:otherwise>
</fest:choose>
<fest:space />
<fest:text>baz</fest:text>
</fest:attribute>
<fest:attributes>
</div>
</fest:set>
</fest:template>
</fest:template>
<fest:get name="block">data</fest:get>
</fest:template>
PS: Есть основные управляющие конструкции типа: set
, get
, if
, else
..., а также собственные расширения которые предполагается использовать через неймспейсы: xml::attr
По поводу модификаторов еще придумал как лучше это организовать, но предполаю что также через расширение (по аналогии с атрибутами)
Если честно, я ничего не понял, аля Jade Егора выглядит лучше.
Да сам еще не определился как это должно выглядеть (мысли в слух) :)
@set block (data) {
div
@xml:attr name as name {
@switch
@when data.disabled:
name = 'foo'
@else:
name = 'bar'
}
@xml::attr class as class {
@if data.foo:
class = 'foo'
@else:
class = 'bar'
class += ' baz'
}
input
@xml::bem::modifier input {
@name checked :
data.checked
@name disabled :
data.disabled
}
@xml::attr {
name: 'verify'
type: 'checkbox'
}
}
Результат:
<fest:template>
<fest:set name="block">
<div>
<fest:attributes>
<fest:attribute name="name">
<fest:choose>
<fest:when test="data.disabled">
<fest:text>foo</fest:text>
</fest:when>
<fest:otherwise>
<fest:text>bar</fest:text>
</fest:otherwise>
</fest:choose>
</fest:attribute>
<fest:attribute name="class">
<fest:choose>
<fest:when test="data.foo">
<fest:text>foo</fest:text>
</fest:when>
<fest:otherwise>
<fest:space />
<fest:text>bar</fest:text>
</fest:otherwise>
</fest:choose>
<fest:space />
<fest:text>baz</fest:text>
</fest:attribute>
<fest:attributes>
<input type="ckeckbox" name="verify">
<fest:attributes>
<fest:attribute name="class">
<fest:text>block__input</fest:text>
<fest:if test="data.checked">
<fest:space />
<fest:text>block__input_checked</fest:text>
</fest:if>
<fest:if test="data.disabled">
<fest:space />
<fest:text>block__input_disabled</fest:text>
</fest:if>
</fest:attribute>
<fest:attributes>
</input>
</div>
</fest:set>
</fest:template>
Т.е. в своем примере я не убиваю работу с атрибутами, классами и основыми управляющими конструкциями. В тоже время, здесь есть возможно расширять синтаксис как угодно (пример с модификаторами)
UPD: обновил пример