|
Snakeskin
|
Обновил версию, исправил кучу багов и добавил пару новых фич, написал статью на хабр, где очень подробно описал всё, ссылку приложил к первому посту и в readme на гитхабе.
|
Цитата:
кст, инвайтом не поделишься? :p |
Цитата:
|
Цитата:
Напиши статью, и тебе скорее всего дадут инвайт. Я на хабре редко сижу и кармы у меня там нема. Цитата:
PS: я удивлён, что данный пост здесь не вызывал никакой реакции, даже не закидали какашками :) Ведь тема шаблонизации для ЖС больная и уже есть овер9000 велосипедов, но буду считать, что людям просто лень читать мой пост :) |
Цитата:
Добавка отражений в Хабах - клик по текущему JavaScript* (при редактировании В апреле опять спад интереса... Карочь самое вдумчивое отношение на Хабре - осенью Зы: я б диннотексты во второй части - "Синтаксис шаблонов Snakeskin", "Директивы" , - Т. е каждый подобный подраздел с кодами убрал под спойлер: имя на спойлере - имя данного подраздела, тогда бы страх "многотекста" рассеялся *Возможно вслед за скрытым спойлером с именем раздела(или до) - короткий резъюм в пару строк. |
Цитата:
* сомневаюсь в том что в наследовании есть смысл. * Также я задумываюсь о более активном применении data-bind, а его у тебя нет. * Сильно не нравиться то что шаблоны оказывают в глобальной области видимости. Думаю неймспейсы должны быть обязательными. в остальном пока без эмоций. |
Цитата:
Если использовать ООП при построение UI, то наследование в шаблонах это просто волшебно, пример из реального проекта: выпадающее меню -> стилизованный селект -> поле ввода с автокомплитом, логично что у этих UI идёт цепь наследования JavaScript, но также у них наследуются шаблоны и тем самым мы избавляемся от копипасты. |
Deff,
да я в общем, не гонюсь ни за кармой, ни за славой, но пасиб за советы:) Эта шаблонка, как наверно и всё остальное, что я делаю - делаю в первую очередь для себя и своих задач, а статью писал как размышления для самого себя и дока для моих наёмных работников, ну если кто захочет использовать в своих проектах, то мне безусловно будет приятно:) Меня просто удивило, что такая холиварная тема осталась незамеченной:) Когда я работал в Яндексе, то мы каждый день спорили с другими отделами чей шаблонизатор лучше:) |
Добавил в "Веб-разработку" :)
|
глупый вопрос - в боевых проектах использовал или сделал и положил на полку? :)
UPD: не знаю, как оно там с точки зрения парсинга, но смущает, что: - циклы {forEach a => el, i} {end} - ифы {if a > 3} {end} - вичи :) {with obj} {end} - темплейты {template test(obj)} {end} - прото {proto a} {end} - блок {block a}Вася{end} заканчиваются на {end} в то время, как - cdata {cdata} {end cdata} на {end cdata}. Не порядок, однако. На первый взгляд, из-за того, что окончания для многих блоков единообразно, сложно будет отследить вложенность блоков. Привет бейсик, здравствуй наглядность: {forEach a => el, i} {next} {if a > 3} {end if} {with obj} {end with} {template test(obj)} {end template} {proto a} {end proto} {block a}Вася{end block} {cdata} {end cdata} |
Цитата:
Я очень жирно использую ООП и мне крайне важно было максимально гибкая система наследования в шаблонах, а также одинаково удобная система использования на сервере (node.js) и клиенте. Ошибки и новые фичи исправляю по мере необходимости. На этой недели собирался сделать большой фикс, так как всплыли некоторые оч противные баги (вроде невозможности декларировать константы внутри прототипа). С cdata проблема в том, что содержимое вырезается по регулярке до анализа шаблона и построения дерева, поэтому возможна следующая проблема: {cdata} {end} {end} Фактически содержимое внутри блока cdata никак не обрабатывается, поэтому я могу написать что угодно, но в данном случае нужно либо как то экранировать {end} теги внутри области (как например это делается в xml), тому кто пишет шаблон или ввести особый закрывающий тег, как сейчас сделано у меня. А так я не вижу смысла делать для каждой директивы свой закрывающий тег - это имхо не круто, тем более с точки зрения парсинга это не даёт профита, т.к. по шаблону у меня строится дерево, в котором видна вся вложенность блоков. Ну а чтобы было видно человеку - нужно делать отступы :) {template foo(params, type = 'static')} {if type === 'static'} муахахаха! {else} {block bar} ... {end} {end} {end} |
kobezzza, спасибо за ответ.
По поводу {end} я категорически тебе предлагаю сделать так, как написал выше. Причина - удобство. Я вижу какой тег закрыт :) Пример // начало кода // много букв {end} {end} смысл примера в том, что я не вижу шапки кода, только подвал. Т.о. для того, чтобы отличить одно от другого, придется скроллить. Иными словами, на этом уровне все кошки серы (почти все). Можно замутить опрос, но на мой взгляд это скорее минус, чем плюс. |
еще можно пойти по пути html
{foreach} a => el, i} {/foreach} {if a > 3} {/if} {with obj} {/with} {template test(obj)} {/template} {proto a} {/proto} {block a}Вася{/block} {cdata} {/cdata} т.е. я к тому, что должно быть видно, какой тег закрыт Цитата:
Почему развел демагогию: присматриваюсь к твоему шаблонизатору :D |
Цитата:
Цитата:
UPD: Я протупил, писать {end что то} можно уже сейчас, т.е. {forEach data => el, i} {end forEach} Т.к. парсер обрабатывает директиву, как {названиеДирективы любые условия}, а в случае с end условий нет, т.е. можно писать что угодно и это ничего не сломает :) {forEach data => el, i} {end это конец моего блока} |
Добавил поддержку закрытия директив через / , как в xml. Новая версия доступна на гитхабе.
{template foo()} {/template} Раз уж зашла тема, скажу что в следующей версии добавлю: 1) Переменные. Я долго противился этому введению, пока пару раз сам не встал на грабли, когда константы не могут их заменить. В отличии от констант переменные нельзя будет явно переопределить в дочернем шаблоне, а только через переопределение родительского блока или прототипа или константы на которую переменная ссылается. Т.е. {template base()} {block foo} {var a = 1} ... {end} {end} {template child() extends base} /// Переопределяем блок и заодно переменную внутри него {block foo} {var a = 2} {end} {end} 2) Циклы. Есть задачи, вроде: сгенерить для select-а номер года от 19.. до ныненшнего, а с итератором такое решение выглядит немного странно. {for var i = 0; i < 10; i++} ... {end} вместо {forEach new Array(10) => el, i} ... {end} 3) Также улучшу и задокументирую некоторые нестандартные фичи, о которых сейчас знаю только я :) В остальном будет исправление известных ошибок и написание новых тестов. |
kobezzza, отлично, спасибо :)
|
Почти закончил работу, над новой версией, скорее всего во вторник выложу, но возник вопрос, когда переписывал модуль обработки scope, а именно:
Мой шаблонизатор поддерживают директиву with (в JS with не используется), которая позволяет явно указывать контекст поиска свойств объекта, т.е. : {template foo()} {my = { name: 'Koba', age: 23 }} {with my} <p>Имя: {name}</p> <p>Возраст: {age}</p> {end} {end} Это очень удобно, особенно когда у нас ссылка вроде myObj.someProp1.someProp2. А также в моём шаблонизаторе есть "волшебная" директива proto, которая позволяется создавать что-то вроде микрошаблонов. {template foo()} {proto logo} <img src="..." alt="моё супер лого" /> {end} <div class="header"> ... {apply logo} </div> <div class="footer"> ... {apply logo} </div> {end} Думаю смысл кода выше всем понятен, но всё же поясню: внутри директивы proto мы декларируем часть шаблона, которая не будет выведена сразу после декларации, а только после того, как мы её вызовем, с помощью директивы apply. А теперь собственно суть проблемы: сейчас директива proto всегда использует глобальный scope шаблона, т.е. {template foo()} {my = { name: 'Koba', age: 23 }} {with my} {proto gen} <p>Имя: {name}</p> <p>Возраст: {age}</p> {end} /// Будет ошибка, т.к. прототип всегда использует глобальный контекст шаблона {apply gen} {end} {end} Проблема решается в общем то довольно тривиально: нужно просто поместить директиву with внутрь прототипа, но я подумал, а что если прототипы будут при декларации получать родительский scope, т.е. пример выше сможет корректно работать, т.к. директива proto находится в контексте with и будет его наследовать. Как вам такое предложение? |
Цитата:
{template foo()} {my = { name: 'Koba', age: 23 }} {proto gen} <p>Имя: {name}</p> <p>Возраст: {age}</p> {end} {with my} {apply gen} {end} {end} |
Цитата:
|
Цитата:
with obj1 .prop = 2; // магическая точка очень кстати end with Цитата:
|
Цитата:
Цитата:
По умолчанию раньше у всех прототипов где бы они не были объявлены всегда был был глобальный контекст (относительно шаблона). В новой же версии контекст по умолчанию привязан к родительскому (как я описывал постами выше). Ща пью кофе, делаю финальные тесты, рефакторинг и обновляю доку:) К вечеру всё буит наверно |
интересно глянуть. сейчас демо с гитхаба ругается
Snakeskin Error: Missing closing or opening tag in the template, node: (class: undefined, id: templates)")! теги все там закрыты, так что хз чего он хочет |
Цитата:
UPD: не были закомитены собранные файлы, теперь всё работает |
Фух, вроде закончил работу на версией 2.3. На обновление доки сегодня наверно уже сил не хватит, отложу на завтра:)
Итак, почти 5 полных дней (рабочих :)) я потратил на разработку новой версии Snakeskin, что было сделано: 1) Полностью переписана часть, отвечающая за анализ и обработку параметров директив: В прошлых версиях директива with работала крайне криво и только на выводе значений (теперь её поддерживаю почти все директивы), т.е. конструкция ниже не работала бы {with myObj} {a + b} {end} Теперь же with работает ровно так, как от него этого ожидают, т.е. можно использовать сложные выражения и т.д., а также появилась возможность указать модификаторы, проще показать: {with myObj} {with foo} {with deep} {a} /// эквивалентно myObj.foo.deep.a {#a} /// эквивалентно myObj.foo.a {#2a} /// эквивалентно myObj.a {#3a} /// эквивалентно a {@a} /// эквивалентно a {@@a} /// доступ к супер-глобальной переменной, об этом чуть позже {end} {end} {end} Как видите, теперь появилась возможность точно указывать контекст поиска: модификатор # указывает на то, что искать свойство нужно на один with блок вверх, а если после решётки указать цифру, например, #2, то на два блока вверх и т.д. Следует заметить, что # - это более короткая запись #1. Модификатор @ позволяет сказать, что поиск значения объекта должен осуществляться вне блоков with. Также теперь в выражениях появилась возможность использовать составные фильтры, т.е. /// Для переменной a и b отдельно применяется фильтр ucfirst, /// а затем ко всему выражению фильтр truncate {(a|ucfirst) + (b|ucfirst) |truncate} Чтобы ввести составной фильтр, нужно просто взять его декларацию в круглые скобки, как это сделано на примере выше. Как и с глобальными фильтрами можно использовать несколько фильтров подряд, а также передавать значения в фильтр: {(a|ucfirst|remove 'foo') + (b|ucfirst) |truncate} {e = ('foo'|ucfirst)} В догонку к фильтрам: исправлена ошибка при использовании побитового ИЛИ (|), теперь считается, что если после | идёт или цифра или пробел, то это операция ИЛИ, а не фильтр: {0|1} {0 | 1} /// ИЛИ {a = 1} {0| a} {0 | a} /// ИЛИ {0|round} {0 |round} /// Фильтр 2) Супер-глобальные переменные. На самом деле эта фича существовала уже давно, но сейчас я её немного облагородил. Если вкратце: /// Объявляем супер глобальную переменную {a = 1} {template foo()} {@a} /// Получаем значение супер глобальной переменной {with ...} {@@a} /// Получаем значение супер глобальной переменной внутри {end} // Объявляем супер глобальную переменную внутри шаблона {@e = 1} {end} Такие переменные хранятся в Snakeskin.Vars. 3) Директива var. Эта директива позволяет объявлять переменные внутри шаблона. В отличии от констант переменные могут менять значение в ходе программы, но не могут явно переопределятся в дочернем шаблоне, как константы. {template foo()} {var a = 1} {a = 2} {a = 3} {end} 4) Директивы циклов: for, while, repeat/until {for var i = 0; i < 10; i++} ... {end} {var i = 100} {while i--} ... {end} {var i = 100} {repeat} ... {until i--} 4) Статичный scope для proto Как я уже писал выше: теперь при декларации proto блока, он наследует родительский scope. 5) Сахарок: Для директивы data добавлена короткая форма записи: {{ ... }}; Теперь функцииям-шаблонам в JS ставится свойство name. Внутри шаблона доступны локальные переменные TPL_NAME и PARENT_TPL_NAME. В остальном исправления множества ошибок и т.д. ЗЫ: И смешно и грустно: первая версия шаблонки была ~400 строк, а эта уже почти 2.7к |
Зарелизил версию 2.3.4 с исправлением некоторых ошибок.
Также теперь проект добавлен в репозитарий npm, следовательно для простой установки можно сделать: npm install -g snakeskin Обновилось консольное АПИ. Подробности в доке на гитхабе или в консольке -h. Обновилась документация на гитхабе. Тем кто юзает продукты JetBrains теперь можно оч просто настроить file watcher для шаблона. Поиграться с шаблоном можно здесь: http://jsfiddle.net/NAPWB |
Цитата:
|
Цитата:
|
правильно понимаю, что это http://jsfiddle.net/NAPWB/3/ должно работать?
|
Цитата:
UPD: поправил, бага наитупейшия была, а воспроизводилось только на пробельных отступах :) Спс за репорт! |
kobezzza,
ие нехочет {template helloWorld(name)} up - заработало |
Цитата:
|
Цитата:
В целом, на мой взгляд, перспективный шаблонизатор. upd: кстати, что насчет производительности? http://jsperf.com/javascript-templat...off-extended/2 |
Цитата:
Скорее всего всякие микро-темплейтинги делают трансляцию быстрее, т.к. весь их алгоритм - это сплит по разделителю и new Function. У меня в проекте ~300 шаблонов и их трансляция это примерно 5-10% от общей сборки проекта и на моей машинке делается где то 0.7 секунд, а для сравнения Google Closure Templates делает это секунд за 20 (хз, почему так долго, особенно учитывая, что он написан на яве). Но это всё лирическое отступление:) Если у тебя 10-ок шаблонов и ты компилишь их на клиенте, то это в любом случае сделается очень быстро даже в ИЕ6 и даже на телефоне:) А если у тебя целая куча шаблонов и ты компилишь их не на сборке проекта, то стоит задуматься, а всё ли ты делаешь правильно:) Да и вообще прекомпиляция это даже удобнее: 1) Скомпиленые шаблоны можно пропустить через минификатор вместе с остальным JS и это лишит головной боли с экспортом свойств, если сжатие идёт например через GCC. 2) Скомпилиный шаблон работает "мгновенно", т.к. его не нужно предварительно парсить и т.д. 3) Шаблоны можно и нужно разбивать на разные файлы, а не лепить всё в кучу, и тут всё оч просто с таким подходом. 4) В JS я просто вызываю функцию, а не думаю: "ой, это шаблон, его нужно скомпилить" 5) Не нужно тащить транслятор на клиент: а в моём случае это почти 3к строк. В общем Snakeskin задумывался как шаблонизатор с прекомпиляцией, но а поддержка live-исполнения сделана исключительно для отладки:) Оптимизация ради оптимизации - это пустая трата времени. |
Цитата:
Цитата:
Цитата:
|
Цитата:
Если ты хочешь автоматизировать процесс компиляции на PHP, то просто нужно на сервак поставить любую VM яваскрипта и из пыхи запустить процесс (по моему команда exec, не помню уже). Или ты имел ввиду использование в качестве серверного шаблонизатора? То тогда на пыхе не оч удобно будет, а на той же ноде, то ещё проще :) ЗЫ: http://screencast.com/t/iU7AgM6jvjaj что может быть проще?) |
Цитата:
|
Как тебе такой подход:
// about.tpl @set about (data, value = 0) { &.title: data.title, &.text: value } // me.tpl @set me < about (data) { &.title: data.title } // index.tpl @include about, me @get about { title: title 1 } @get me ({ title: title 2 }, 1) Будет транслированно в: <div class="about"> <div class="about__title"> title 1 </div> <div class="about__text"> 0 </div> </div> <div class="about me"> <div class="about__title me__title"> title 2 </div> <div class="about__text me__text"> 1 </div> </div> Как это будет выглядеть в твоем случае? :) PS: 1. div по-умолчанию, хотя можно определить любой тег, используя селекторную нотацию h1.header 2. & означает конкатенацию (автодополнение для bem) 3. + планирется полная абстракция от тегов (web components), ну и data-binding |
monolithed,
Похоже на BEMHTML от Яндекса чем-то :) Хорошее решение. У меня будет как то так: {template about(data, value = 0)} <div class="{PARENT_TPL_NAME} {TPL_NAME}"> <div class="{PARENT_TPL_NAME ? PARENT_TPL_NAME + '__title' : ''} {TPL_NAME}__title"> {data.title} </div> <div class="{PARENT_TPL_NAME ? PARENT_TPL_NAME + '__text' : ''} {TPL_NAME}__text"> {value} </div> </div> {end} {template me(data, value = 0) extends about} {end} Замечания: Пример у меня выглядит немного неуклюже, т.к. для нормального BEM подхода у меня написана либа, работающая вместе с шаблонизатором. Т.е. сам шаблонизатор изначально не привязан к конкретной технологии, но его гибкость позволяет легко написать любой над-сахар. |
Цитата:
|
Часовой пояс GMT +3, время: 05:28. |
|