- PVSM.RU - https://www.pvsm.ru -
Во время разработки своего экспериментального WEB-проекта [1] на Node.JS, о котором я рассказал в двух предыдущих [2] статьях [3], я столкнулся с проблемой выбора шаблонизатора. Несмотря на то, что готовых решений существует довольно много, мне не удалось найти то, которое бы удовлетворяло меня на 100%. Так родился JUST [4].
github.com/visionmedia/jade [5]
Этот шаблонизатор достаточно популярен среди Node.JS разработчиков. Он обладает хорошим функционалом и скоростью работы, но содержит и спорные моменты:
github.com/visionmedia/ejs [7]
Маленький, быстрый и довольно удобный шаблонизатор. Он прост и понятен, но, увы, для большого проекта его функционала вряд-ли будет достаточно. Из коробки он не умеет собирать страницу из частей, хотя это частично решается небольшими дополнениями, как например это сделано в Express [8]. Но костыли — не наш метод.
github.com/fadrizul/twigjs [9]
Как можно догадаться из названия, это порт довольно популярного PHP-шаблонизатора на JavaScript. Этот шаблонизатор неплохо сбалансирован, но и его нельзя назвать идеальным. Для реализации логики разработчики добавили свой синтаксис, который должен облегчить разработку. Мне эта идея не кажется удачной, т.к. это увеличивает порог вхождения в разработку. Будь то PHP или JavaScript, верстальщику или программисту придётся тратить время на изучение ещё одного синтаксиса, вникать в его логику и следить за его изменениями. Момент очень спорный. Многие разработчики довольны таким синтаксическим сахаром. Я — нет.
Эти три качества легли в основу JUST. Шаблонизатор это она из важнейших деталей любого WEB-проекта. Если не считать полностью закешированные страницы — он работает при каждом запросе. Чтобы на любом этапе развития проекта работа с шаблонами приносила только радость, шаблонизатор изначально должен быть таким, чтобы его хотелось лизнуть.
Простой пример шаблонов JUST:
page.html
<%! layout %> <p>Page content</p>
layout.html
<html> <head> <title><%- title %></title> </head> <body><%*%></body> </html>
Код, который сгенерирует из этих шаблонов и переданных данных конечный HTML, выглядит так:
var JUST = require('just'); var just = new JUST({ root : __dirname + '/view' }); just.render('page', { title: 'Hello, World!' }, function(error, html) { console.log(error); console.log(html); });
Более полный пример можно найти в репозитории на GutHub [10].
На клиенте всё работает аналогично. Сначала необходимо подключить JUST к странице:
<script src="https://raw.github.com/baryshev/just/master/just.min.js" type="text/javascript"></script>
Теперь JUST можно использовать точно так же, как в Node.JS:
var just = new JUST({ root : '/view' }); just.render('page', { title: 'Hello, World!' }, function(error, html) { console.log(error); console.log(html); });
Т.к. в клиентской версии загрузка шаблонов осуществляется при помощи AJAX, они должны находиться на том же домене, что и страница со скриптом.
В качестве root JUST может использовать JavaScript-объект. Иногда такая возможность может пригодиться.
var just = new JUST({ root : { layout: '<html><head><title><%- title %></title></head><body><%*%></body></html>', page: '<%! layout %><p>Page content</p>' } });
Шаблон разбирается на части простым алгоритмом и превращается в функцию, которая выполняет конкатенацию строк, а в качестве параметров принимает данные для формирования результата. Эта функция кешируется, и далее работа идёт только с ней. Повторный разбор происходит только при перезагрузке приложения или при изменении файла шаблона (если опция watchForChanges установлена в true). Такой подход позволяет получить отличную производительность. На «Что делать? [1]» страницы с разбором шаблона генерируется 20-60мс. Без разбора шаблона (из закешированной функции) за 1-2мс. Учитывая то, что разбор происходит только 1 раз для каждого шаблона, а приложение живёт долго — временем разбора шаблона можно пренебречь.
Данные передаваемые шаблонизатору видны «насквозь» всем шаблонам участвующим в построении страницы. Это избавляет от необходимости передавать их вручную в родительские или дочерние шаблоны, однако возможность ручной передачи данных есть.
Вывод данных в шаблон осуществляется при помощи простой конструкции:
<%- varName %>
Данные вставляются в шаблон как есть, т.е. они не проходят никакую предварительную обработку в шаблонизаторе.
В шаблонах можно использовать полноценный JavaScript код. Например:
<% for (var i = 0; i < articles.length; i++) { %> <% this.partial('article', { article: articles[i] }); %> <% } %>
или
<% if (user.authenticated) { %> <%@ partials/user %> <% } else { %> <%@ partials/auth %> <% } %>
или
<%- new Date().toString() %>
Как показала практика, наследование это очень удобный инструмент, позволяющий строить страницу снизу вверх. Операция наследования в JUST выглядит следующим образом:
<%! layout %>
или
<% this.extend('layout', { customVar: 'Hello, World!' }); %>
если нужно передать в родительский шаблон дополнительные параметры. Операция наследования может находиться в любом месте шаблона, но удобнее её вставлять либо сверху либо снизу.
В родительском шаблоне на месте вставки дочернего нужно вставить такую конструкцию:
<%*%>
или
<% this.child(); %>
Эта операция позволяет подключить внешний шаблон в место вызова, предав ему необходимые параметры, если это необходимо.
<%@ partial %>
или
<% this.partial('partial', { customVar: 'Hello, World!' }); %>
если нужно передать в подключаемый шаблон дополнительные параметры.
Этот механизм чем-то похож на наследование. В родительских шаблонах, кроме места для вставки дочернего, можно объявлять места для вставки блоков. Содержимое этих блоков определяется в дочерних шаблонах. В отличии от наследования, переопределение блоков работает до самого верха, т.е. из любой шаблон может определить содержимое блока любого из своих родителей, а не только непосредственного. Для определения блока используется следующая конструкция:
<%[ blockName %> <p>This is block content</p> <%]%>
или
<% this.blockStart('blockName'); %> <p>This is block content</p> <% this.blockEnd(); %>
В любом из родительских шаблонов нужно определить место вставки для блока используя следующую конструкцию:
<%* blockName %>
или
<% this.child('blockName'); %>
Блоки можно переопределять. В результат попадает блок, определённый на самом нижнем уровне.
Почти во всех шаблонизаторах присутствуют наборы фильтров, которые позволяют производить манипуляции с данными на этапе генерации HTML. Например: экранирование тегов, приведение регистра символов, обрезание строк. Такой подход в корне не верный и этому функционалу тут не место.
В комментариях на этот счёт обязательно появятся возражения вроде: «Этот подход не соответствует MVC. Экранирование тегов относится только к представлению данных в виде HTML/XML, а мы можем захотеть вывести их в формате где экранирование не нужно, например — PDF. Поэтому этот функционал должен быть именно в шаблонизаторе».
Предотвращая споры по этому поводу, сразу отвечу на этот вопрос.
Шаблонизатор не является компонентом «V» модели MVC в чистом виде. Как частный случай он может им быть, но совершенно не обязан. Компонент «представление» может включать в себя как средство непосредственного построения ответа (шаблонизатор), так и средства подготовки данных для конкретного представления. На этом этапе данные можно подготовить и закешировать. Такой подход позволит не выполнять огромное количество лишних операций с данными. Учитывая, что строковые операции довольно медленные, пренебрегать этим нельзя. Если вам всё равно сколько раз выполнится замена строки, один или десять тысяч, то у меня для вас плохие новости.
Ещё один модный тренд — изобретать свой собственный синтаксис шаблонов. Чаще всего это сводится к выпиливанию скобочек и т.п. уменьшению конструкций языка. Выигрыш от этого очень сомнительный. Разработчик должен знать ещё один синтаксис и надееться на то, что автор шаблонизатора в новой версии не сделает его «ещё удобнее», и его шаблоны продолжат работать после обновления. Нативный синтаксис языка знают все, кто причастен к разработке. Новый разработчик может сразу же влиться в дело, а не вникать в работу супер-удобного синтаксиса. Верстальщики, которым чаще всего нет особого дела до того, как там программисты режут их творения на куски, могут сами внести небольшие изменения в шаблоны и даже подправить логику, т.к. JavaScript они всё же знают по долгу службы.
Один неприятный момент в JUST всё же имеется. Если в шаблоне есть обращение к неопределённой переменной, то при его обработке возникнет ReferenceError при обращении к несуществующей переменной. Хочу немного сказать о причинах такого поведения и о том, почему не получилось это исправить.
Для обращения к переменным внутри шаблона я вижу три пути:
На данный момент я не знаю как обойти эту ситуацию без дополнительного синтаксического анализа шаблона. Если кто-то видит красивое решение для данной проблемы — мне будет очень интересно о нём прочитать. Пока это нужно принять как особенность данного шаблонизатора. Хотя в каком-то смысле такой подход дисциплинирует и не даёт раскидываться неопределёнными переменными.
Автор: BVadim
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/2564
Ссылки в тексте:
[1] экспериментального WEB-проекта: http://chtodelat.com
[2] предыдущих: http://habrahabr.ru/blogs/nodejs/138071/
[3] статьях: http://habrahabr.ru/blogs/nodejs/138629/
[4] JUST: https://github.com/baryshev/just
[5] github.com/visionmedia/jade: https://github.com/visionmedia/jade
[6] мозг: http://www.braintools.ru
[7] github.com/visionmedia/ejs: https://github.com/visionmedia/ejs
[8] Express: http://expressjs.com/guide.html#view-partials
[9] github.com/fadrizul/twigjs: https://github.com/fadrizul/twigjs
[10] репозитории на GutHub: https://github.com/baryshev/just/tree/master/examples
Нажмите здесь для печати.