Блочная верстка с наследованием - что это такое

Собственно, про блочну верстку я писал уже не раз, и даже как-то приводил ее пример в этом топике: livestreet.ru/blog/wishlist/13103.html (и мне кажется, что имеет смысл прочитать топик по ссылке, прежде чем читать этот; хотя и не обязательно — можно и после этого).

Но то ли пример там слишком сложный, то ли за один раз не получается все объяснить. Поэтому я решил еще один топик запостить с простыми и понятными (как мне кажется) примерами.

Вот, в упрощенном виде, типовой макет страницы LS-сайта:


И, предположим, мы решили сделать новую верстку, чтобы итоговый HTML-код выглядел примерно так:
<body>
  <section class="b-view">
    <header class="b-view-header">
    </header>
    <section class="b-view-middle">
      <section class="b-content">
      </section>
      <section class="b-sidebar">
      </section>
    </section>
    <footer class="b-view-footer">
    </footer>
  </section>
</body>


Давайте разберем, как это реализуется с помощью нынешнего подхода к построению LS-шаблонов, и как я это предлагаю делать.

«Кусочная» (или «ленточная») верстка
Как это делается в нынешней парадигме LS-верстки (я называю ее «кусочной» или «ленточной»). Мы разбиваем HTML-код на части («кусочки»), которые потом пихаем в разные шаблоны. Как минимум, мы делаем из макета два «кусочка»:
header.tpl
<body>
  <section class="b-view">
    <header class="b-view-header">
    </header>
    <section class="b-view-middle">
      <section class="b-content">
Footer.tpl

      </section>
      <section class="b-sidebar">
      </section>
    </section>
    <footer class="b-view-footer">
    </footer>
  </section>
</body>


Теперь, мы хотим создать, допустим, три шаблона – для главной страницы (index.tpl), для списка топиков (topics.tpl) и для списка блогов (blogs.tpl). И мы делаем это так – для каждого шаблона пишем ту часть, которую хотим воткнуть секцию b-content, а потом собираем окончательные шаблоны по кусочкам:
index.tpl
{include file=header.tpl}
{include file=index-list.tpl}
{include file=footer.tpl}

topics.tpl
{include file=header.tpl}
{include file=topics-list.tpl}
{include file=footer.tpl}

blogs.tpl
{include file=header.tpl}
{include file=blogs-list.tpl}
{include file=footer.tpl}


«Блочная» верстка
А вот как то же самое можно сделать в «блочной» верстке с наследованием.
index.tpl
<body>
  <section class="b-view">
    <header class="b-view-header">
    </header>
    <section class="b-view-middle">
      <section class="b-content">
      {block name="b-content"}
      <!—здесь контент для домашней страницы -->
      {/block}
      </section>
      <section class="b-sidebar">
      </section>
    </section>
    <footer class="b-view-footer">
    </footer>
  </section>
</body>

topics.tpl
{extends file=index.tpl}
{block name="b-content"}
<!—здесь контент для страницы topics -->
{/block}

blogs.tpl

{extends file=index.tpl}
{block name="b-content"}
<!—здесь контент для страницы blogs -->
{/block}


Т.е. Smarty при компиляции шаблона topics.tpl возьмет шаблон index.tpl и заменит в нем блоки, которые были переопределены (в нашем случае блок b-content). Если вдруг нам надо не заменить блок, а только добавить к блоку дополнительное содержимое, то в наследуемом шаблоне нужно использовать в директиве {block} ключевые слова append или prepend:
{block name="b-content" append}
{block name="b-content" prepend}

Тогда содержимое будет добавляться в конец или в начала блока, не трогая контента родителя.

Возможно, кто-то помнит, как я говорил в предыдущих обсуждениях, что блок заменяет два хука, так вот – это оно то самое и есть.

И, конечно, наследование может быть сколь угодно глубоким (т.е. у конечного шаблона может быть много предков), и блоки могут быть вложены друг в друга.

Плюсы и минусы «блочной» верстки
Самый большой плюс в том, что уменьшается число файлов, которые подгружаются движком при отображении уже скомпилированных шаблонов. Дело в том, что директивы Smarty {include} в процессе компиляции шаблонов преобразуются в PHP-директивы include. Т.е. компилируется каждый шаблон по отдельности, и во время рендеринга страницы все шаблоны считываются и подключаются. Но ведь это в нашем примитивном примере всего три кусочка подключаются, в реальности их несколько десятков (в среднем – 35-40), и все они считываются при каждом отображении страницы каждому посетителю сайта.

Когда используется «блочная» верстка с наследованием, то при компиляции Smarty собирает из всей цепочки один единственный итоговый шаблон. И именно этот единственный скомпилированный шаблон использует при рендеринге страницы.

Разумеется, не стоит предаваться излишнему фанатизму и навсегда отказываться от использования директивы {include}. Но, сдается мне, вполне реально уменьшить число инклудов на порядок. А это значит, что можно уменьшить число файловых операций при рендеринге страницы примерно в 10 раз.

Из самых серьезных минусов я пока вижу только то, что это несколько непривычно, и надо иначе настроить свои мозги при разработке шаблона с «блочной» версткой. Возможно, еще какие-то минусы есть, которые я не вижу. Если кто-то увидит – дайте знать.

41 комментарий

avatar
Лично я двумя руками за.

В идеале(на примере опыта работы с другими фреймворками):
1. Наследоваться от index.php по умолчанию на уровне php, без явного прописывания в каждом tpl файле и возможностью горячей смены как раз таки указыванием.
2. Опустить использование
{block name="b-content"}
<!—здесь контент для страницы blogs -->
{/block}

и по умолчанию то что лежит вне блоков относить к блоку b-content(или просто content), а что бы рассовать данные по другим блокам использовать запись то что указана выше, но с нужным именем блока.
3. Данные штуки с наследованиями позволяют выбирать общую структуру верстки и например для страницы логирования или регистрации(вспоминаем header.lite.tpl) она может коренным образом отличаться. Поэтому index.php стоит вынести в папку layouts, а там уж при желании создавать сколько угодно вариантов скелетов.

Я уверен что придеться танцевать с бубном и делать хаки для реализации подобных вещей, но они упращают код, делают его более минималистичным. Просто желание :)
avatar
По какой то причине писал .php вместо .tpl. Прошу читать index.tpl :)
avatar
Пункт 2 я не понял
avatar
Все это используется в рельсах. Я покажу на примере проекта диаспоры, т.к исходники открыты.

Тут используется разметка именуемая haml поэтому опускаем html код и видим

- content_for :head do
  = javascript_include_tag :profile

Эта запись аналогична
{block name="b-content"}
<!-- подключение js -->
{/block}

Который в свою очередь будет вставлен в layouts/application.html.haml, в нашем варианте в блок располагаемом в хэде.

Все что определено в файле(у нас это шаблон экшена) и находиться вне content_for будет передано и вставлено сюда(в лэйот), у нас если мы не имеем возможности создавать безымянные блоки можно по умолчанию согласовать и использовать имя content.

Это описания того о чем я говорил и один из примеров реализации этой схемы в рельсах которая успешно используется разработчиками. Конечно подключение скриптов в хэд нельзя назвать хорошим примером, но можно прогуляться по другим лэйотам и увидеть что он находит применение в различных частях макета
avatar
Я этот механизм блоков не сам придумал, это «родной» механизм Smarty, который введен в третьей версии (еще три года назад). Я лишь попытался объяснить, как это можно более эффективно использовать.

Безымянные блоки в Smarty не предусмотрены, поэтому блоки нужно объявлять явно, как в шаблонах-родителях, так и в шаблонах-потомках.

Что касается названий самих блоков в моих примерах, то они тоже не «от балды» даны, это влияние методологии БЭМ, о которой рассуждали до этого. Поэтому, если говорить шире, я постарался совместить тут два понятия «блоки» — и от БЭМ, и от Smarty. Т.е. это термин «двойного назначения», и класс БЭМ-блока совпадает с именем Smarty-блока:
<section class="b-content">
  {block name="b-content"}
  {/block}
</section>

Здесь <section class=«b-content»> — это для CSS-классов,
а {block name=«b-content»} — для наследования шаблонов.

Пример в топике сильно упрощен. А на деле практически все теги типа <section class=«b-...»> должны внутри себя содержать Smarty-блоки {block name=«b-...»}, где класс секции и имя блока совпадают
avatar
Я понимаю но в каждом tpl файле прописывать
{extends file=index.tpl}
{block name="b-content"}
<!—здесь контент для страницы blogs -->
{/block}

не будет ли это слегка утомлять, да будет выйгрыш в скорости, но по сути красивее станет только главный лэйот, ну и динамичность возрастет — можно будет прям в экшене изменять другие части шаблона.

Если реализуем 1-ый пункт то на большинство tpl файлов будет приходиться такой код
{block name="b-content"}
<!—здесь контент для страницы blogs -->
{/block}

И это уже хорошо, но еще лучше не обрамлять наш html код если мы и так знаем что нам нужно его поместить в контент и получить следующий вариант
<!—здесь контент для страницы blogs -->


Но при всем при этом у нас сохраняеться возможность наследования, возможность использовать блоки и при необходимости код будет выглядеть так:
{extends file=login.tpl}
тут лежит то что попадет в b-content шаблона
{block name="b-sidebar"}
<!—здесь контент для страницы сайдбара -->
{/block}
и еще лучше если блоки не будут жестко привязаны к расположению в разметки - т.е текст можно писать тут не смотря что выше определен блок.


Я еще раз повторюсь это в идеале, просто мысли, реализовывать их не обязательно, но если есть возможность то так должно быть удобнее. Будет меньше повторяемого кода.
avatar
Мысль понял. Но согласно синтаксису Smarty, если шаблон начинается с директивы {extends ...}, то в этом шаблоне все, что лежит вне блоков просто игнорируется. Поэтому избавиться от объявления блоков просто так не получится.

И вот жесткой привязки нет. Напр., в наследуемом шаблоне можно и так прописать:
{extends file=login.tpl}
{block name="b-content"}...{/block}
{block name="b-sidebar"}...{/block}

и так:
{extends file=login.tpl}
{block name="b-sidebar"}...{/block}
{block name="b-content"}...{/block}

Работать будет одинаково, т.к. порядок блоков тут не играет значения, они будут вставляться в родительском шаблоне туда, где изначально объявлены.

Это уже мое предложение — сделать стандартом жесткую связку HTML-блоков и Smarty-блоков в изначальном макете.
avatar
Идея вообще суперская, но значит либо хаки писать, либо смириться :)
В любом случае уже будет выйгрыш, так что если будет голосование я за вашу идею!
avatar
топик хороший.

думаю что нужно проголосовать каким-то образом за идею, честно подразумевая, что:
  • такие изменения приведут к неработоспособности старых шаблонов на версии, где будет применена данная технологи и, как следствие, возрастет число недовольных (мало шаблонов в каталоге на новую версию и т.п и т.п.
  • большая запутанность для верстальщиков, которые не очень активны в программировании. Им нужно будет время на освоение новой технологии.

плюсами будет:
  • рост производительности
  • чуть меньшее количество файлов в шаблонах

попрошу дополнить + и -
avatar
такие изменения приведут к неработоспособности старых шаблонов на версии, где будет применена данная технологи и, как следствие, возрастет число недовольных (мало шаблонов в каталоге на новую версию и т.п и т.п.

Что то я вас не понял, это заложено в Smarty, то есть технология будет работать и старая и новая, потому что для Smarty, и та и та не является старой или новой, просто это две схемы работы, которые были придуманы давным давно, так что все будет работать, то есть и старые шаблоны и такие новые.

я не прав? поправьте :)
avatar
Что то я вас не понял, это заложено в Smarty, то есть технология будет работать и старая и новая, потому что для Smarty, и та и та не является старой или новой, просто это две схемы работы, которые были придуманы давным давно, так что все будет работать, то есть и старые шаблоны и такие новые.

вы правы на счет технологий. для вас, как верстальщика шаблонов в целом все верно. Но для разработчиков плагинов, это означает что для шаблона по старой технологии нужно будет добавлять свою страницу по старому методу через инклуды шапки, с заданием меню, футера. По новой же технологиии нужно будет писать:

{extends file=index.tpl}
{block name="b-content"}
И тут что нужно добавить.
{/block}
avatar
а то есть вы намекаете, что шаблон на блочной верстке, не будет работать с плагинами на include?

по мне так сомнительная паника :) мне кажется должно все работать.

То есть смешение технологий, не должно повредить процессу… но все покажут тесты :) как без них.
avatar
а то есть вы намекаете, что шаблон на блочной верстке, не будет работать с плагинами на include?
нет, инклуды технически будут работать, но структура шаблона будет другой. хеадер.тпл, например, в шаблоне по новому стилю может не оказаться вовсе.
avatar
это да может, но это уже беда верстальщика, так как желательно конечно соблюсти основную архитектуру, и мелочи например уже адаптировать, как и делаю многие плагины, начинают писать адаптации для шаблонов.
avatar
дело в том, что сохраняя обратную совместимость растет количество расходов и потенциальных ошибок, поэтому если переходить на новый стандарт, то сразу в пределах одной версии, а не растягивать перенос на несколько версий.
avatar
согласен, но нужно создать прецедент, прежде чем господа разработчики LS )) возьмут и вот так махом перейдут без особых причин на новую структуру шаблона :)

нужны факты, доказательства, тесты :)

я вот не верю в сказки, да и знаю товарища орта, по многи ситуациям, оно и понятно, не всегда есть время, даже если это реальная штука…
avatar
102 версия и так никаких изменений кроме как технических пока не имеет, раз уж такая пляска, то можно и шаблоны перелопатить
avatar
1. ну вам виднее

2. я советую изменить структуру шаблона ветки комментариев… я вот напишу скоро про это топик, как я топик с >1000 комментами оптимизировал в 2 раза, не трогая php код вообще, а благодаря только html, надеюсь это внесут в версию :) посмотри, посмотрим…
avatar
пишити, конечно!
возможно тогда 102-я станет одной из самых быстрых, безопасных и надежных версий!
avatar
ну надеюсь, не станет одной из самых быстрых :))… а вот из крайних последних версий точно станет самой быстрой :)
avatar
Я все про старое :) Поддержка инжекции плагинами в блоки(замена блочных хуков) — livestreet.ru/blog/wishlist/13103.html#comment203344
Есть реализация этого механизма?
  • ort
  • 0
avatar
М? Так я ж там в комментарии и пример привел. Попробуй объяснить, что непонятно, тогда я попробую разъяснить в ответ :)
avatar
ты там пример результата привел, а как его добиться?
конкретно вот этот момент:
задается последовательное наследование, начиная от родителя register.tpl:

register.tpl -> register1.tpl -> register2.tpl
как задать эту цепочку?
avatar
Можно явно в самих шаблонах задавать, а можно при вызове метода Smarty:
$smarty->display('extends:register.tpl|register1.tpl|register2.tpl');
avatar
так имя шаблона в разных плагинах может быть любым, количество плагинов может быть любым
avatar
У меня нет готового решения. Но ведь цепочка экшенов тоже может быть какой угодно длинной, и эта проблема успешно решается. Так и тут наверняка можно найти решение.
avatar
понятно, про решение я и спрашивал первым комментом
avatar
не ну мне все понятно =)

я с такой непоследовательностью работаю давным давно, только не в LS, а проектах на Питоне, ребята с которыми я работаю все только блочный тип и юзают, но не отказываясь от инклуд.

вобщем я попробую сделать пример одой странице на основе шаблона bootstrap =) посмотрим что получится.

Если что буду спрашивать, как и договорились :)
комментарий был удален
комментарий был удален
комментарий был удален
avatar
Спасибо за топ. Весьма любопытно. Интересно было бы узнать насколько это добавляет производительности на практике. Т.е стоит ли овчинка выделки, на самом-то деле.
avatar
вопрос не только в производительности — сильно упрощается структура шаблона
avatar
ОК. Я согласен что это более чистая концепция. И уже ради этого оно стоит усилий. А вот добавит ли оно производительности — вопрос открытый. Если у нас (а это типичный случай) вся статика и БД на том же сервере что и LS, то там столько io операций на каждый страничный запрос ходит, что уменьшение количество файлов в шаблоне мало что улучшит. Если весь выигрыш только в io, конечно.
avatar
Да, выкладки, в основном, теоретические. Но вот число подключаемых скомпилированных шаблонов при рендеринге страницы при нынешнем подходе (порядка 40 файлов) — это вполне себе реальная цифра. И то, что их число уменьшится — это тоже однозначно так. Как это реально отразится на скорости — хз

Чтоб проверить все это на практике, нужно сделать две верстки — «кусочную» и «блочную» — одного и того же дизайна. Причем, не просто тест, а реальную верстку, чтоб по полной все отработало в обоих случаях. Только так проверить можно. Но захочет ли кто-то на это угрохать кучу времени? ;)

Но тут еще есть плюсы, правда, не такие очевидные: напр., будет, как минимум, один файл (макет), в котором видна вся HTML-структура страницы. И все шаблоны будут иметь правильную DOM-структуру, т.е. не будет такого, что какой-то тег открывается в одном шаблоне, а закрывается в другом, что нередко нехилого гемора добавляет.
avatar
я берусь за это :) версия шаблона уже есть на include, как раз хотел опыт с бранчами на гите, вот и открою отдельный бранч с блочными.

и проведем опыты :) буду рад если будите помогать. Своих ребят я уже попросил помочь, которые на Питоне фигачат.
avatar
Данный вариант + определение правил для css = огромный шаг вперед в развитии LS

Я сейчас пытаюсь структуризировать для себя работу с оформлением, css и прочим для макетов и плагинов.
avatar
Добавлю небольшой минус такого подхода: вёрстка логическими блоками повлечёт за собой накладные расходы на наследование, что со своей стороны может незначительно повлиять на производительность. В защиту включения файлов (лапши, ленточной вёрстки) также можно привести следующее: многие пользователи Лайвстрита применяют средства кэширования опкода, из чего следует, что операции над скомпилированными файлами проводятся в памяти, а не на диске. Не факт, что переход на наследуемые шаблоны в стиле Джанго принесёт сколько-нибудь заметный выигрыш при том, что эта миграция представляется в значительной степени трудоёмкой.

Также хотелось бы указать на тот факт, что в примерах содержатся семантические ошибки (каскад section, footer внутри section и т. д.) а также необоснованно смешиваются логические и функциональные блоки. Возможно, последнее является следствием тонкой границы между блочной вёрсткой (логическая группировка элементов в уникальные группы, пример — #head на главной Яндекса) и вёрсткой независимыми блоками (функциональная группировка элементов для повторного использования в любом контексте, как на картинке ниже).



Из соглашения по именованию классов я могу заключить, что автор предлагает использовать методологию БЭМ, которая в своём применении вне Яндекса требует особой осторожности. Она была разработана как часть законченного производственного процесса, включающего в себя средства сборки (борщик), оптимизации (имго/цссо) и развёртывания, которые при всём желании за уши в лайвстрит не притащишь. Описанная путаница, вытекающие из неё некорректные упрощения и inhouse-ориентированность БЭМа могут только сбить с толку верстальщика, рассматривающего переход на наследуемые шаблоны.

Поскольку этот пост предназначен для верстальщиков, то я бы предложил поправить примеры, чтобы ориентировать их на хорошие практики. К сожалению, все шаблоны лс, которые я видел, показывают, что их авторы не в полной мере знакомы с HTML и эту ситуацию следует исправлять.

Я бы предложил избегать одинаковых имён в логических и функциональных блоках, что соответствует упомянутой выше методологии. Напомню также, что блок в синтаксисе смарти можно записать лаконичнее:
{block "title"}
avatar
так должны же быть правила наследование. смысл его пихать там где оно не надо.
avatar
Первый «минус» — расходы на наследование — совсем не существенный, т.к., как я уже писал, Smarty очень разумно поступает с наследуемыми шаблонами — он собирает на этапе компиляции все в один итоговый шаблон. Т.е. это все равно, как если б верстальщик на каждый URL делал один сплошной шаблон с «макушки» до «хвоста». Поэтому здесь немного увеличивается время компиляции шаблона, а уже скомпилированные шаблоны работают быстрее.

Насчет миграции — согласен, это потребует усилий, и, возможно, не очень маленьких. Надо все взвесить и определиться — стоит игра свеч или нет. ИМХО, да, стоит.

По семантике спорить не буду, охотно верю, что в этом плане мой пример далек от идеала. Тем более, что действительно — грань между логическими и функциональными блоками весьма зыбкая. Внедрять в полной мере методологию и технологии БЭМ для шаблонов ЛС, думаю, вряд ли целесообразно. Но в целом в предлагаемом подходе влияние БЭМ, конечно есть. В т.ч. и в именовании классов.

Кстати, как раз для того, чтобы не сбивать с толку верстальщика, я и предлагаю одинаковые имена в блоках. Понятно, что далеко не всегда логическому блоку обязательно должен ставиться в соответствие блок функциональный. Но, на мой взгляд, функциональному блоку всегда можно поставить в соответствие блок логический. Разве нет? И в этом случае однозначное сопоставление имен предпочтительней, ИМХО. Так врестальщику будет проще.

Т.е., проще говоря, я предлагаю Smarty-блоки оборачивать в теги с одноименными классами (за исключением блоков внутри тега head, разумеется). А при именовании классов опираться на БЭМ. И, что крайне важно, предлагаю сформулировать не только правила именования классов, но и определить несколько десятков стандартных классов, обязательных к использованию.

Разумеется, все сказанное — ИМХО, не претендующее на истину в последней инстанции. Тем более, что я все ж больше программер, а не верстальщик. Поэтому показать верстальщикам хорошие практики я вряд ли смогу. Лучше будет, если какой-нибудь опытный верстальщик предложит альтернативный подход, более грамотный и целостный, либо разовьет мои предложения, исключив из них ошибки и неточности. Рад буду обсудить.
avatar
функциональному блоку всегда можно поставить в соответствие блок логический.
Не всегда, поскольку логика имеет отношение прежде всего к представлению и иерархии документа, а функциональность — к представлению и поведению. Дробление логических блоков до уровня функциональных приведёт к необоснованному разрастанию количества файлов. Например, отдельным функциональным блоком может быть логотип, но вряд ли кому-то придёт в голову выделять его в логический блок в шаблоне. Напомню, что предназначение функциональных блоков — повторное использование в контексте одного документа, а логических — повторное использование в разных документах.

Преимущества, которые даёт повсеместное внедрение наследования и независимых блоков, прежде всего почувствуют коллективы разработчиков, создающие сайты на основе лайвстрита, а для разработчиков шаблонов и энтузиастов-одиночки, использующих движок на своих сайтах значительной разницы не будет. Кроме, конечно, дополнительных финансовых вложений и затрат времени, которые прямой выгоды им не принесут. На мой взгляд, такие радикальные перемены в зрелом продукте, к которому уже можно отнести лайвстрит, вряд ли будут уместны.

Я также убеждён, что наследование не сможет сыграть роль фактора, который привлечёт дополнительный интерес к движку со стороны верстальщиков. Инклюды понятны и логичны для начинающих, а для более квалифицированных специалистов, повседневно применяющих обсуждаемые методологии, нужна несколько иная мотивация, которой в лайвстрите нет и, возможно, никогда не будет.

Разработчики шаблонов для лайвстрит исторически всегда использовали в качестве основы дефолтный шаблон, внося в него лишь косметические изменения разной степени. Очевидно, что люди, сосредоточенные прежде всего на работе с css, вряд ли станут широко использовать наследование и методологию БЭМ (правильное понимание которой составляет отдельную проблему и требует определённой квалификации).

Если я правильно тебя понял, твоё предложение направлено на популяризацию движка. Я считаю, что она должна реализовываться совершенно иными средствами, которые уже многократно обсуждались. Массовое распространение технологически примитивных конкурирующих продуктов может только подтверждить моё мнение.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.