javascript-шаблонизация для начинающих на примере lodash template
В своих постах я часто упоминаю javascript-шаблонизацию. Underscore и lodash-шаблоны, может, видели. И недавно понял свою ошибку. Я пишу про шаблоны так, как будто прям каждый обязан знать, что это такое. А если не знает, то легко загуглит. А давайте-ка вместе и погуглим. Чуть не первая статья, с хабра, цитирую
— Лучшим выбором оказываются шаблоны, потому что это приводит к более чистому базовому коду и лучшему процессу работы с ними.
Хм, ну окееей. Погуглим еще. Сайт developer.mozilla.org, серьезные ребята, почитаем.
— Шаблонными литералами называются строковые литералы, допускающие использование выражений внутри. С ними вы можете использовать многострочные литералы и строковую интерполяцию.
Че-то это вообще не то. Это что-то из es6. Ну и третья попытка:
— Неплохо бы использовать механизм шаблонизации на стороне клиента, чтобы отделить поведение приложения от его внешнего вида
Я в свое время читал похожие умные слова и ни фига не понимал. Если Вы понимаете, то читать дальше будет не интересно. Зайдите на хабр и прочитайте лучше там. А вот те, кто по этим фразам с трудом улавливает, о чем речь, но хочет понять, оставайтесь. Попробуем вместе разобраться, что такое javascript-шаблонизация, зачем она нужна и когда применяется.
Люблю случаи из жизни. Давай представим. Ты работаешь в веб-студии фронтендщиком. На пару с бекендщиком пилите сайты. Пусть его зовут Серега. Ты верстаешь шаблоны, отдаешь ему html-файлы, а он их натягивает на cms-ки или фреймворки. Какой-нибудь битрикс или laravel, не суть. Серега твой бро, он не воротит нос от html-тегов и вообще отличный парень. Нормально относится к тому, что ты просишь в зависимости от разных условий проставлять тегам нужные классы или не выводить определенные блоки. И все у вас хорошо, спокойно работаете, а по пятницам вместе бухаете в баре.
Однажды к вам приходит дизайнер и говорит, а чо мы список товаров делаем всегда одинаково? Пагинация, фу, прошлый век же. Давайте замутим подгрузку товаров аяксом, когда прокручиваем до конца страницы. Вы такие с Серегой думаете, а чо, прикольно. Набрасываете схему, ты отлавливаешь, когда юзер докрутил до подвала страницы, дергаешь аяксом запрос, а он отдает тебе html со списком товаров. А ты такой ррраз и $(‘#products’).append(response). Нормальная схема, все довольны. Ты делаешь верстку, несложную, примерно такую
Идешь к Сереге и объясняешь, мол, верстка такая, но есть две детали. Если товара нет в наличии, то в тексте у product__store пишешь «Нет в наличии». И еще не забудь кнопке button при этом проставить атрибут disabled. Серега говорит, без проблем, все сделаю. Ты дальше возишься с прокруткой страницы и отправкой аякс-запроса, он формирует html по твоей верстке и данным из базы. Потом стыкуетесь, все отлично, начальство и дизайнер довольны.
Проходит неделя, к тебе снова идет дизайнер. Говорит, чувак, давай когда товара нет в наличии, немного стили изменим. Ну там заголовок сереньким сделаем, а кнопку «В корзину» вообще уберем. Ты говоришь, хорошо. Думаешь, так, стили поменять, это нужно класс модификатор навесить, типа -available прямо на li.product. А кнопку можно убрать через display: none, но лучше бы вообще убрать. Идешь к Сереге, рассказываешь это все, он делает, опять все довольны.
Магазин развивается, начальство придумывает новую фишку. Давайте сделаем рейтинги товаров. Нарисуем звездочки у каждого товара и пусть люди кликают. Вы с Серегой собираетесь на совет. Он говорит, мол, я заведу поле rating в базе, ты дергаешь post-запрос, отправляешь мне айдишник товара, а я там разберусь, в базе инкремент сделаю, рейтинг товара увеличиваю. Ты говоришь, все нормально, только звездочки в верстку не забудь добавить. И тут он отвечает, рано или поздно, он такое скажет. Мол, бро, я все понимаю, но и ты меня пойми. Мне твои звездочки до одного места. Я тут запросы в базу пишу и редисы настраиваю, давай ты версткой сам будешь заниматься. Я тебе отдам данные о товарах, в json-чике, а ты делай с ними, что хочешь. Хочешь, звездочки добавляй, хочешь, классы навешивай, хочешь, тексты меняй в зависимости от наличия. Ты немножко недоволен, нормально же все раньше было. Но в душе понимаешь, Серега прав. И говоришь, отличная идея, давай так и сделаем.
Серега готовит запрос и тебе рассказывает. Вот смотри, теперь тебе будет отдаваться не готовый html, а json-массив такого вида.
Ты говоришь, ну нормально, чо я массив что ли распарсить не смогу и в html добавить? И начинаешь делать. Сначала у тебя получается что-то вроде этого
var products = JSON.parse(response); var html = ''; products.forEach(function(product) < // формируем строку html для одного товара и добавляем ее в переменную html >); $('#products').append(html);
Пока ничего особенного, пока не начали реализовывать вот это
// формируем строку html для одного товара и добавляем ее в переменную html
Вспомним те условия, что у нас есть: класс-модификатор -available, разный текст в зависимости от наличия и убрать кнопку корзины, если нет товара. И начинаем писать что-то такое
Уф. Читать, конечно, тяжко, но пока еще можно. Вспомним, для чего мы это затеяли? Да, нам же звездочки рейтинга нужно добавить. Изящный код в нужное место и все.
Ну так себе, выглядит диковато, но ладно. Главное, теперь не надо Серегу дергать каждый раз со всякой фигней. И всей версткой ты управляешь на клиенте. Ты гордо говоришь, что вы с Серегой перенесли рендер html с сервера на клиент. Начальство довольно.
Но только эйфория недолго длится. Прибегает менеджер и говорит: так, у нас акция, последние 5 товаров со склада продаем со скидкой 20%. И красным выделяем такие товары. Обсуждаете с Серегой. Он говорит, слушай, давай я добавлю в респонс json-a поле count — сколько товаров на складе осталось. А ты на фронте скидку сам посчитаешь. А то сам знаешь, сегодня прибегут, скажут последние 5 товаров с 20% скидкой и цвет красный, а завтра последние 10 и скидка 15%. А цвет зеленый. А ты на фронте все разрулишь, все равно к тебе будут с этим бегать. Ты такой, ну окей, сделаю.
И начинаешь соображать. Нужно переменную завести, типа осталось мало товаров. И по ней проставлять еще один класс-модификатор. Ну и цену высчитать с учетом скидки. Вроде несложно. Итоговый код будет такой.
Как оно? Такое чувство, что с этим пора что-то делать. Это мы пока очень простые варианты рассматриваем, а что будет, если посложнее? У меня от кавычек одинарных и двойных уже в глазах рябит. Ошибиться в такой мешанине легче легкого. Чуть промахнулся, удалил кавычку лишнюю, попробуй потом найди, где косяк и почему все валится. Надо что-то делать. Для начала гуглим. А что гуглить-то? Хрен знает даже, как вопрос задать. Чешешь репу дальше.
Вдруг ты вспоминаешь, что у Сереги же должны были быть такие же проблемы. Раньше, когда он верстку сам натягивал. Какая разница, на бекенде, на фронтенде, у него такие же условия и вычисления. Идешь к нему. Показываешь код, спрашиваешь, как он раньше делал? Он говорит, у меня шаблонизатор есть, я туда данные загоняю, а на выходе готовый html. Спрашиваешь, а как это выглядит? Серега показывает, вот, например, у тебя такая строка
html += '' + price + ' руб.';
А у меня в шаблоне была бы такая
Блин, вроде ж то же самое, но по читабельности не сравнить. Ты говоришь, ладно, это самый простой случай, а как сделать такую штуку, посложнее?
html += '' + (product.available ? 'В наличии' : 'Нет в наличии') + '';
Серега отвечает, да примерно так же.
Или еще лучше, текст записать заранее в переменную, вот так
var availableText = product.available ? 'В наличии' : 'Нет в наличии');
А в шаблоне сделать так
Вот так точно лучше, кратко и ясно. А вычисления все отдельно. И 5 звездочек нечего лепить руками, циклом прогнал по массиву от 1 до 5 и все. А то завтра нужно будет 10 звездочек поставить, чтобы еще не копипастить, а просто заменить 5 на 10. Еще Серега что-то рассказывает про MVC, отделение логики приложения от представления, про модели и вьюхи, но ты слушаешь плохо, и так уже есть над чем подумать.
Ты подумал и теперь, имея некий багаж знаний, гуглишь уже целенаправленно. И инфу найти легче. Возможно, ты уже пользовался библиотеками lodash или underscore. Ты удивляешься, но в них есть функция работы с шаблонами _.template. Если про нее не знать, то читая доку и внимания не обратишь. Но теперь-то ты представляешь, что это такое, и смотришь внимательнее.
Оказывается, чтобы работать с шаблонами через lodash (да и вообще с любым шаблонизатором), нужно сделать 2 вещи: подготовить данные и верстку, то есть сам шаблон. Это и есть то самое отделение логики от представления. Или почти что MVC. Данные о товаре это условно модель, а шаблон — вьюха или представление.
Давайте сначала простейший пример, как работает _.template() в lodash (в underscore точно так же). Заглянем в документацию.
Там приведен код
var compiled = _.template('hello !'); compiled(< 'user': 'fred' >); // => 'hello fred!'
Такие смешные закорючки — это место для переменной user. Точнее, не переменная, а поле объекта < 'user': 'fred' >. user можно и без кавычек, просто
Смотрим дальше. Написать в одной строке ‘hello !’ легко. Но у нас немаленькая верстка из нескольких элементов. Чтобы сделать адекватно, применяют хитрый шаг. Шаблоны не сразу записывают в строки, а предварительно прямо в html в специальном теге
Или type=»text/x-template», можно по-разному, но суть одна — содержимое этого тега не исполняется как javascript, и мы из него вытаскиваем строку шаблона через простой $(‘#template-name’).html(). Давайте попробуем написать шаблон для нашего случая
Ну как? Сравните с тем, что было выше, где складывали строки. Читалось это совершенно иначе. В кавычках уже не путаемся. Видим, что у элемента с классом product могут быть еще 2 класса-модификатора classAvailable и classLast. Условие if (available) then вывести кнопку button тоже логически срисовывается. И даже цикл for, где мы выводим список li, не вызывает страха. Да, немножко непривычны эти постоянные , а в getDataForProductItemTemplate вычислим новое поле classNew и добавим его в return. Жизнь становится гораздо проще.
Подведу итоги. Надеюсь, мы смогли разобраться, зачем нужна javascript-шаблонизация. Или по крайней мере поняли ее плюсы и будем пытаться применять. На мой взгляд, синтаксис шаблонов в undescore/lodash не самый симпатичный. Но есть и куча других, например, handlebars. Он лаконичнее и приятнее. Например, там переменная выводится не , а > Кому как, но мне симпатичнее. Плюс в handlebars есть свои плюшки, типа вычисляемых полей и прочего. Впрочем, синтаксис lodash меня вообще не напрягает, поэтому именно его я и использую в своих статьях. И скажу по секрету, его можно переопределить под себя. Например, вместо
- © Webdevkin
- 2015 — пока не надоест
- ВКонтакте
- webdevkin@gmail.com
- RSS-лента
HTML-шаблонизаторы
Есть много способов сэкономить время и упростить жизнь разработчика. Но они кажутся такими сложными и непонятными, что знакомство с ними постоянно откладывается. И зря: сегодня мы расскажем, как автоматизировать работу, используя вспомогательные инструменты.
Шаблонизаторы для HTML — один из таких способов.
Какие проблемы решают шаблонизаторы
Главное преимущество шаблонизаторов — они избавляют от необходимости писать повторяющийся код несколько раз. Это кажется не такой большой проблемой, но всё же разработчикам часто приходится тратить на это время.
При вёрстке шаблонов принято выделять повторяющиеся блоки в компоненты, чтобы использовать их на других страницах, но иногда один из них требуется изменить. Если речь идёт о внешнем виде компонента, всё просто: вы меняете его CSS-код, и он обновляется везде. Но как быть, если нужно переделать HTML-код сложного компонента? Сначала придётся внести правку в код на одной странице, а затем найти все подобные компоненты и провести аналогичные изменения с каждым.
Простой пример — страница с карточками товаров. Если вы вносите правку в одну карточку, то вам придётся исправлять и остальные — вручную. Просто удалить старые и скопировать новые с уже заполненными данными не получится.

Такие изменения могут касаться не только сложной разметки, но и более простых элементов вроде контента в ссылках. Типовая задача — разметить главное меню так, чтобы при клике на ссылку открывались соответствующие шаблоны страниц. Это упрощает процесс разработки и демонстрации. Но если у вас в проекте 30 страниц и вы вносите правку в ссылку в главном меню, то вам придётся делать это 30 раз.
Эти ситуации не катастрофичны, но приводят к следующим проблемам:
- Необходимость постоянно держать в голове контекст проекта. То есть помнить, где есть похожие компоненты, чтобы вносить правки везде. Особенно неудобно, если вы вернулись к проекту после перерыва.
- Возникновение ошибок на этапе разработки и внесения правок. Можно забыть раскатать правки на все компоненты, и где-то вылезут баги. Причём не из-за плохой вёрстки, а просто потому, что вы забыли скопировать код.
- Увеличение времени разработки и внесения правок, особенно при потере контекста.
Вместо этого можно было потратить время на улучшение проекта. Давайте разберёмся, как именно шаблонизаторы помогут избавиться от лишних проблем.
Уже всё знаете про шаблонизаторы и просто решили освежить знания?
Ответьте на 6 вопросов, чтобы узнать, насколько вы разбираетесь в шаблонизаторах.
Как шаблонизаторы помогают решать проблемы?
Рассказывать про работу шаблонизаторов будем на примере принципа их работы, без отсылки к конкретному инструменту.
Создание шаблонов
Представим обычный сайт на вёрстку. У него есть шапка и подвал, которые повторяются на всех страницах, а центральная контентная часть везде отличается. В таком случае вы можете создать файл, поместить в него шаблон и указать, в какое место подключать контент. А в другом файле прописать содержание страницы и указать, что она должна встраиваться в шаблон.
Рассмотрим это на условном, несуществующем синтаксисе.
Файл раскладки
Сайт про сайт
Здесь мы определяем основной шаблон всего сайта, с шапкой и подвалом, и указываем конструкцию, которая будет указывать, куда должен размещаться контент.
Файл каталога и новостей
Страница каталога
Карточка 1 Карточка 2 Карточка 3 Страница новостей
Новость 1 Новость 2 Новость 3
В отдельных файлах мы указываем, какой шаблон для них используется и как они будут выглядеть. На выходе мы получим две страницы с одинаковой шапкой и подвалом и разным контентом.
Самое важное, что теперь мы можем менять шаблон сразу для всех страниц и это происходит автоматически, а нам в случае необходимости не надо за ними следить и менять руками много файлов.
Создание компонентов
Компоненты — это как шаблоны, но наоборот. В случае с шаблоном мы указываем, где он будет использоваться для необходимого контента. При работе с компонентами мы сначала его определяем, а потом указываем место, где его надо вызвать.
Если шаблоны больше подходят для глобальной раскладки сайта, то компоненты предназначены для использования маленьких блоков, начиная от простых кнопок и заканчивая более сложными составными компонентами, например карточками товара или целым блоком с избранными товарами.
В примере ниже компонентом может быть как каждый блок по отдельности, так и группировка из всех четырёх блоков.

В качестве примера абстрактной реализации рассмотрим реализацию каталога.
В отдельном файле описываем саму карточку товара со всей структурой.
Каталог магазина
А уже в другом файле, в котором необходимо подключать компонент, вызываем его.
Каталог магазина
Благодаря этому становится проще использовать компоненты и собирать из них более сложные блоки. При изменении компонента в одном месте он будет обновляться везде, а значит, мы не забудем внести в него правки.
Работа с данными
Не может же быть так, чтобы при вставке девяти карточек проекта они все стали одинаковыми? Что делать, если мы хотим видеть разный контент в одинаковых компонентах?
Создавать новые компоненты под разный контент или копировать его не придётся. Шаблонизаторы умеют разделять данные и их внешний вид, вставляя полученные данные в код компонента.
То есть мы можем не использовать данные внутри компонента, а передать их при вызове, чтобы они подставились в нужные места. Разберёмся, что это значит.
При описании компонента мы говорим, что его контент не статичный, а берётся из объекта с данными, которые будут в него переданы.
>
Каталог магазина
И при вызове компонента мы передаём ему эти данные, чтобы на выходе у нас был компонент в нужной структуре, но с разным содержанием.
>
Каталог магазина
Благодаря этому мы можем использовать разный контент в одинаковых компонентах, при необходимости менять классы у разных блоков и легко управлять интерфейсом.
Интерактивность
Внутри шаблонизаторов есть такие полезные возможности, как условные выражения и циклы. Мы не будем подробно останавливаться на них, а лишь расскажем, в каких сценариях это может пригодиться.
Условные выражения позволяют вам менять что-то внутри компонента в зависимости от данных внутри. Это делает компоненты более гибкими. Например, у вас есть компонент кнопки в трёх цветах. Вместо создания трёх отдельных компонентов изменение класса цвета можно сделать внутри одного, и он будет меняться в зависимости от условий.
Циклические конструкции освобождают от копирования повторяющихся компонентов. Вы указываете данные, которые надо перебрать, и компонент, в который их надо вывести. На выходе вы получаете нужное количество данных в нужном компоненте. Это удобно при создании карточек товаров и списков — новостных и прочих.
Эти возможности позволяют писать код ещё быстрее и более гибко, избегая при этом лишних ошибок.
Какие шаблонизаторы бывают
Все пункты выше мы рассматривали на примере общих принципов работы шаблонизаторов. Известные инструменты направлены на решение примерно одних и тех же задач, но между ними есть отличия, несмотря на общую идеологию.
Когда мы проводили исследование навыков, необходимых на рынке, мы поняли, что из инструментов наибольшей популярностью пользуются Pug и Twig. Они отличаются синтаксисом и окружением, а также внутренним функционалом.
Pug — написан на Node.js и компилирует строгий синтаксис в конечный HTML-файл. То есть он не работает на лету и по факту всё равно превращается в HTML. Его надо собирать через сборщик. Инструмент представляет собой довольно нестандартный синтаксис, который непривычен в начале, но быстро осваивается и даёт хороший профит.
Twig — шаблонизатор, написанный на PHP, часто используется при вёрстке шаблонов. Он собирается на сервере и не требует предварительной сборки, но работает в окружении локального сервера.
В зависимости от окружения проекта лучше подойдёт тот или иной шаблонизатор, но самое главное, что любой из них позволяет значительно упростить себе жизнь.
Подготовка к работе с шаблонизаторами
Шаблонизаторы не выглядят сложным инструментом, но с ними всё не так просто.
- Первое, чему необходимо научиться, — правильно использовать принципы. Верно выделять шаблоны, компоненты и правильно их комбинировать. В нужный момент использовать условия и циклы, чтобы процесс написания и поддержки становился проще.
- У шаблонизаторов есть особенности синтаксиса, которые не всегда очевидны, и некоторые функции не всегда работают так, как от них ожидается.
- И самое важное — необходимо правильно использовать возможности для разных типовых задач, чтобы не усложнять работу.
Всё это постигается опытом и позволяет существенно упростить процессы производства и дальнейшей поддержки проекта.
Какие выводы можно сделать
Шаблонизаторы делают написание кода проще и избавляют разработчика от хранения в голове большого количества контекста. Это позволяет тратить силы на более полезные вещи.
В целом, шаблонизаторы близки друг другу и построены на единой философии в разных инструментах. Но важно научиться принципам, а не конкретному инструменту. А инструмент лучше выбирать под конкретное окружение.
Главное — использовать шаблонизаторы правильно, чтобы действительно оптимизировать работу.
«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.
Эволюция шаблонных систем для JavaScript
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Различных шаблонных систем – много.
Они постепенно эволюционировали и развивались.
В этой главе мы разберём, как шёл этот процесс, какие шаблонки «родились», какие бонусы нам даёт использование той или иной шаблонной системы.
Микрошаблоны
Микрошаблоны (англ. microtemplate) мы уже видели на примере _.template .
Это HTML со вставками переменных и произвольным JS.
Шаблонная система компилирует этот код в JavaScript-функцию с минимальными модификациями, и она уже, запустившись с данными, генерирует результат.
Достоинства и недостатки такого подхода:
Недостатки
- Жёстко привязан к языку JavaScript.
- При ошибке в шаблоне приходится лезть внутрь «страшной» функции
Достоинства
- Простая и быстрая шаблонная система
- Внутри JS-функции доступен полноценный браузерный отладчик, функция хоть и страшна, но понятна.
Код в шаблоне
Включение произвольного JS-кода в шаблон, в теории, позволяет делать в нём всё, что угодно. Но обратная сторона медали – шаблон вместо внятного HTML может стать адским нагромождением разделителей вперемешку с вычислениями. Что рекомендуется делать в шаблонах, а что нет?
Можно разделить код на два типа с точки зрения шаблонизации:
- Бизнес-логика – код, формирующий данные, основной код приложения.
- Презентационная логика – код, описывающий, как показываются данные.
Например, код, получающий данные с сервера для вывода в таблице – бизнес-логика, а код, форматирующий даты для вывода – презентационная логика.
В шаблонах допустима лишь презентационная логика.
Кросс-платформенность
Зачастую, нужно использовать один и тот же шаблон и в браузере и на сервере.
Например, серверный код генерирует HTML со списком сообщений, а JavaScript на клиенте добавляет к нему новые по мере появления.
…Но как использовать на сервере шаблон с JavaScript, если его основной язык – PHP, Ruby, Java?
Эту проблему можно обойти. На сервер, использующем PHP, Ruby, Java или какой-то другой язык, дополнительно ставится виртуальная машина V8 и настраивается интеграция с ней. Почти все платформы это умеют.
После этого становится возможным запускать JavaScript-шаблоны и передавать им данные в виде объектов, массивов и так далее.
Этот подход может показаться искусственным, но на самом деле он вполне жизнеспособен и используется в ряде крупных проектов.
Прекомпиляция
Эта шаблонка и большинство других систем, которые мы рассмотрим далее, допускают прекомпиляцию.
То есть, можно заранее, до выкладывания сайта на «боевой сервер», обработать шаблоны, создать из них JS-функции, объединить их в единый файл и далее, в «боевом окружении» использовать уже их.
Современные системы сборки (brunch, grunt с плагинами и другие) позволяют делать это удобно, а также хранить шаблоны в разных файлах, каждый – в нужной директории с JS-кодом для виджета.
Хелперы и фильтры
JavaScript-вставки не всегда просты и элегантны. Иногда, чтобы что-то сделать, нужно написать порядочно кода.
Для того, чтобы сделать шаблоны компактнее и проще, в них стали добавлять фильтры и хелперы.
-
Хелпер (англ. helper) – вспомогательная функция, которая доступна в шаблонах и используется для решения часто возникающих задач. В _.template , чтобы объявить хелпер, можно просто сделать глобальную функцию. Но это слишком грубо, так не делают. Гораздо лучше – использовать объект _.templateSettings.imports , в котором можно указать, какие функции добавлять в шаблоны, или опцию imports для _.template . Пример хелпера – функция t(phrase) , которая переводит phrase на текущий язык:
_.templateSettings.imports.t = function(phrase) < // обычно функция перевода немного сложнее, но здесь это не важно if (phrase == "Hello") return "Привет"; >// в шаблоне используется хелпер t для перевода var compiled = _.template(" "); alert( compiled() ); // Привет
Свой язык
Для того, чтобы сделать шаблон ещё короче, а также с целью «отвязать» их от JavaScript, ряд шаблонных систем предлагают свой язык.
Шаблон для меню в Handlerbars, к примеру, будет выглядеть так:
Как видно, вместо JavaScript-конструкций здесь используются хелперы. В примере выше > . – «блочный» хелпер: он показывает своё содержимое для каждого элемента items и является альтернативой forEach .
Есть и другие встроенные в шаблонизатор хелперы, можно легко делать свои.
Использование такого шаблона:
// текст шаблона должен быть в переменной tmpl var compiled = Handlebars.compile(tmpl); var result = compiled(< title: "Сладости", items: ["Торт", "Пирожное", "Пончик"] >);
Библиотека шаблонизации Handlebars «понимает» этот язык. Вызов Handlebars.compile принимает строку шаблона, разбивает по разделителям и, аналогично предыдущему виду шаблонов, делает JavaScript-функцию, которая затем по данным выдаёт строку-результат.
Запрет на встроенный JS
Если «свой язык шаблонизатора» очень прост, то библиотеку для его поддержки можно легко написать под PHP, Ruby, Java и других языках, которые тем самым научатся понимать такие шаблоны.
Если шаблонка действительна нацелена на кросс-платформенность, то явные JS-вызовы в ней запрещены. Всё делается через хелперы.
Если же нужна какая-то логика, то она либо выносится во внешний код, либо делается через новый хелпер – он отдельно пишется на JavaScript (для клиента) и для сервера (на его языке). Получается полная совместимость.
Это создаёт определённые сложности. Например, в Handlebars есть хелпер > . > , который выводит содержимое, если истинно условие cond . При этом вместо cond нельзя поставить, к примеру, a > b или вызов str.toUpperCase() , будет ошибка. Все вычисления должны быть сделаны на этапе передачи данных в шаблон.
Так сделано как раз для переносимости шаблонной системы на другие языки, но на практике не очень-то удобно.
Продвинутые кросс-платформенные шаблонизаторы, в частности, Closure Templates, обладают более мощным языком и умеют самостоятельно разбирать и компилировать многие выражения.
Шаблонизация компонент
До этого мы говорили о шаблонных системах «общего назначения». По большому счёту, это всего лишь механизмы для преобразования одной строки в другую. Но при описании шаблона для компоненты мы хотим сгенерировать не просто строку, а DOM-элемент, и не просто генерировать, а в дальнейшем – с ним работать.
Современные шаблонные системы «заточены» на это. Они умеют создавать по шаблону DOM-элементы и автоматически выполнять после этого разные полезные действия.
- Можно сохранить важные подэлементы в свойства компоненты, чтобы было проще к ним обращаться из JavaScript.
- Можно автоматически назначать обработчики из методов компонента.
- Можно запомнить, какие данные относятся к каким элементам и в дальнейшем, при изменении данных автоматически обновлять DOM («привязка данных» – англ. data binding).
Одной из первых систем шаблонизации, которая поддерживает подобные возможности была Knockout.JS.
Попробуйте поменять значение в примере ниже и вы увидите двухстороннюю привязку данных в действии:
Шаблоны — JS: Express
Если проанализировать http запросы к типичному сайту, то можно заметить, что большая часть этих запросов направлена на получение контента, а не его модификацию. Другими словами, основная работа обработчиков состоит в том, чтобы сформировать правильный html и отправить его клиенту (браузеру). Единственный способ для генерации html , с которым мы знакомы, это ручной сбор строчки, содержащей разметку, и отправка посредством метода send .
app.get('/', (req, res) => res.send('Hello World!'); >);
Сказать, что этот способ плох, это ничего не сказать. Кроме того, что это крайне неудобно, существует масса других недостатков в таком подходе. Если заглянуть в историю развития web, то выяснится интересный факт: php появился как средство решения описанной выше задачи, а не как язык программирования.
Задачу по формированию разметки называют шаблонизацией, а конкретные библиотеки для шаблонизации называют шаблонизаторами. Общий принцип работы такой: описываются файлы с разметкой, а библиотека предоставляет функции для загрузки этих шаблонов в код. Во время загрузки происходят необходимые подстановки и шаблон заполняется конкретными данными.
Jinja Like
Классическим примером может служить шаблонизатор jinja из мира питона. Его популярность привела к тому, что в каждом языке есть множество шаблонизаторов, очень похожих и даже работающих так же, как jinja . Поэтому можно говорить о целом классе jinja-like шаблонизаторов.
class="header"> pagename | title >> class="small">authors for author in authors %> if loop.first %> class="first" endif %>> author >> endfor %>
По сути jinja — это хоть и примитивный, но полноценный язык программирования, который вкрапливается в файл с разметкой и расширяет его во время обработки. Несмотря на очевидность этого решения, оно обладает рядом недостатков. Первое — это сложность редактирования такого рода шаблонов. Из-за перемешивания кода с версткой приходится скакать вверх-вниз чтобы добавить/удалить/изменить теги, и то же самое нужно делать с конструкциями самого языка. Этот недостаток может быть не очевиден тем, кто никогда не видел альтернативных решений, и как мы увидим позже, они есть. Второе: в подобных шаблонизаторах текст вне конструкций шаблонизатора, то есть та самая вёрстка, никак не анализируется. Это легко приводит к проблемам типа «незакрытый тег», или семантическому нарушению html , когда неправильно друг в друга вкладываются теги, используются несуществующие атрибуты и тому подобное. И третий немаловажный момент: оформление шаблонов не проверяется автоматическими инструментами, и поэтому стиль будет сильно зависеть от человека.
Pug (Haml Like)
Существует и совершенно другой подход к организации шаблонов. Когда я в первый раз увидел такое, то был немало удивлен. Кажется, что самостоятельно дойти до этого решения очень сложно. Чтобы не томить, сразу покажу пример:
Этот пример почти идентичен тому, что было выше с использованием jinja-like шаблонизатора. Обратите внимание насколько чище шаблон во втором примере и на то, что он почти в два раза короче.
История таких шаблонизаторов берет свое начало с haml , Ruby шаблонизатора, который в мире rails является решением номер один уже очень много лет. После этого оно было скопировано во многие языки, как и jinja . В js мире haml-like шаблонизатор был долгое время известен как jade , и лишь недавно его переименовали в pug .
Попробуем разобраться с основными принципами работы таких шаблонизаторов. Во-первых, это так же язык программирования, но в отличие от jinja-like шаблонизаторов, то что не является кодом, на самом деле не является версткой. Всё, что пишется в pug -шаблонах, будет обрабатываться парсером, другими словами, в haml-like шаблонизаторах вы не можете писать всё что угодно вне управляющих конструкций. Во-вторых, шаблон строится с помощью особого синтаксиса, который задаёт теги в виде имен, а вложенность определяется отступом на следующем уровне.
И, с одной стороны, у вас появляется новый язык и новый способ построения html . Что требует некоторого привыкания, но с другой, преимущества оказываются настолько сильными, что человек, распробовавший подобные шаблонизаторы, вряд ли добровольно вернется на jinja-like библиотеки. Ниже перечислены основные преимущества:
- Шаблон чище, гораздо короче и уже
- Отсутствует проблема незакрытых тегов (т.к. их просто нет)
- Писать и модифицировать такие шаблоны гораздо проще
- Стиль задаётся грамматикой (писать по разному практически невозможно)
- Шаблоны валидируются, и соответствующая библиотека не даст делать совсем злые вещи
- Вставляемые данные по умолчанию всегда экранируются (привет php!)
Интеграция pug с Express выглядит очень просто:
// npm install pug app.set('view engine', 'pug'); app.get('/', (req, res) => const data = title: 'Hey', message: 'Hello there!' >; res.render('index', data); >)
Всё сводится к установке зависимости и установке pug в качестве движка для рендеринга шаблонов. После этого, внутри обработчиков можно начинать использовать метод render . Первый параметр которого — это путь до шаблона, второй — набор параметров для подстановок внутри шаблона.
Это не единственный способ передачи параметров в шаблон. В большинстве случаев они передаются именно вторым параметром в render , но иногда возможны ситуации, в которых у нас есть сквозная функциональность, и было бы крайне неудобно прокидывать их в шаблон в каждом обработчике. Реализуется это через установку свойств в объект res.locals , а в шаблоне эти свойства становятся доступны как переменные. Эту особенность мы будем использовать позже, когда начнем работать с сессиями и аутентификацией. Помните, что злоупотреблять этим способом не стоит, явное лучше неявного. Стремитесь к тому, чтобы код был чистый (использовал чистые функции).
Наследование шаблонов
На практике сайт не всегда состоит из уникальных страниц. Обычно меняется только контентная часть, а вокруг одно и тоже. Часть, которая не меняется, принято называть макетом или лейаутом (layout). Это настолько распространенный кейс, что большинство шаблонизаторов поддерживают механизм для выделения лейаутов. В pug он называется наследованием шаблонов. Ниже приведён пример такого наследования.
block scripts script(src='/jquery.js') body block content block foot #footer p some footer content //- page-a.pug extends layout.pug append scripts script(src='/pets.js') block content h1= title - const pets = ['cat', 'dog'] each petName in pets h2= petName
В шаблоне, который мы используем для рендеринга нашей страницы, пишется специальная директива extends . . В неё передаётся имя окружающего шаблона, который чаще является макетом. В макете определяется блок (или блоки), в которые будет происходить подстановка кусков шаблона. Далее необходимо в шаблоне (не макете) определить такие же блоки и наполнить их контентом. Синтаксис задания блоков в обоих местах одинаковый, только в одном случае блок не содержит тела, а в другом содержит.
Включения
Так же бывает полезным механизм включения, позволяющий выделять из шаблонов общие части и переиспользовать их.
Чистота
На просторах интернета постоянно спорят о том, что может быть в шаблоне, а чего нет. Что является логикой вывода, а что нет. При этом есть ряд правил, которые объективно нарушать не стоит:
- Ни в коем случае шаблон не должен порождать побочных эффектов. В шаблоне нельзя писать в базу, изменять данные на диске и вообще любым способом пытаться влиять на окружающую среду. Шаблон это исключительно чтение.
- Шаблоны должны быть декларативны, никакого изменения состояний, другими словами, если вы начинаете внутри шаблона вводить переменные и изменять их, то, по сути, шаблон превращается в полноценный скрипт, который вы программируете. Не допускайте этого.
- Использовать логику, влияющую на вывод внутри шаблона — это нормально. Если у вас, с точки зрения ui , блок показывается по определённому условию, то вы не сможете этого избежать, единственное о чем нужно помнить, это создавать вовремя правильные абстракции (функции) для избежания дублирования, а так же для выделения бизнес-правил.
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях: