Я уже писал в одной из заметок о шаблонах на JS . В этот раз я рассмотрю диаметральный подход – компилируемые шаблоны. Примерами таких шаблонов могут служить mustache.js, Underscore template и многие другие библиотеки.
В их основе лежит один и тот же принцип: конструктору передается исходный шаблон в виде строки текста; на выходе получаем некую функцию. Эта функция, в свою очередь, возвращает строку, в которой вместо размеченных особым образом фрагментов будут подставляться аргументы, использованные при вызове. Так же в шаблонах может быть реализована простая логика ветвления, циклы или вызов вспомогательных функций.
Интересным аспектом применения компилируемых шаблонов является то, где и как будет храниться исходный текст шаблона и скомпилированный шаблон. На ум приходят несколько вариантов:
- в виде строки в js-файле;
- внутри тега
<script>
с произвольным типом в html; - в отдельном файле;
- компилирование исходного текста шаблона при сборке проекта.
Хранение в js-файле
var templateSource = "<div class=\"welcome\">" +
'<p class="welcome__message">Hello: <%= name %></p>' +
'</div>';
var template = _.template(templateSource);
Плюсы:
- исходный текст шаблонов конкретного модуля сразу доступен для компиляции в момент инициализации этого модуля;
- с большой вероятностью файл, содержащий шаблон, будет закеширован браузером, что в общем счёте сэкономит трафик при регулярном посещении страницы.
Минусы:
- текст шаблона перемежается с логикой;
- содержимое строки в JS нужно правильно экранировать;
- для разных ролей пользователей могут понадобиться разные шаблоны, а это, в свою очередь, может скомпрометировать какую-то часть функциональности пользователей с высоким уровнем доступа.
Хранение в исходном HTML-коде страницы
<script id="template-welcome" type="text/x-template">
<div class="welcome">
<p class="welcome__message">Hello: <%= name %></p>
</div>
</script>
Браузер содержимое тега <script>
считает простым текстом, а так как в атрибуте type
у него указан неизвестный ему MIME-тип, то интерпретировать или отображать он его не станет. Зато содержимое этого тега можно получить после загрузки документа, обратившись к нему по id
.
var templateSource = document.getElementById("template-welcome").innerText;
Плюсы:
- размещается вместе с другой разметкой;
- набор шаблонов или их содержимое может динамически меняться в зависимости от внешних факторов.
Минусы:
- если страница не будет закеширована браузером, то эти шаблоны будут каждый раз загружаться с сервера;
- возможно, потребуется подождать окончания загрузки страницы, прежде чем к элементу, содержащему исходный текст шаблона, можно будет получить доступ.
Хранение в отдельном файле
Шаблоны, ко всему прочему, можно хранить в виде отдельных статических или генерируемых динамически файлов и загружать при необходимости.
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<template id="template-welcome"><![CDATA[
<div class="welcome">
<p class="welcome__message">Hello: <%= name %></p>
</div>
]]></template>
<template id="template-product"><![CDATA[
…
]]></template>
</templates>
Текст шаблона обязательно должен быть заключен в блок CDATA
, чтобы его содержимое можно было получить без изменений.
$.ajax("/static/templates/welcome.xml")
.done(function (templates) {
var templateEle = templates.getElementById("template-welcome");
if (templateEle) {
callback(_.template(templateEle.innerText));
}
templateEle = null;
});
Набор шаблонов загружается с помощью XHR, а затем методом getElementById()
получаем нужный объект, содержащий исходный текст шаблона.
Плюсы:
- с большой вероятностью статический файл, подгружаемый через XHR, будет закеширован браузером, и в дальнейшем некоторое время не будет запрашиваться с сервера;
- можно организовать дополнительный уровень кеширования шаблонов (Local Storage, например);
- наборы шаблонов можно формировать для конкретной комбинации ролей пользователя, что позволит защитить функциональность недоступную текущему пользователю.
Минусы
- дополнительный HTTP-запрос при первом посещении;
- инициализация модуля будет «отложенной» из-за асинхронной загрузки требуемых шаблонов.
Компилирование при сборке
В твиттере и комментариях мне подсказали ещё один способ хранения шаблонов. Каждый шаблон хранится в виде HTML-файла, а на этапе сборки эти файлы компилируются в js-функции и сериализуются прямо на сервере. В итоге клиент будет загружать обычные статические js-файлы.
Плюсы:
- шаблоны будут кешироваться браузером как любые другие статические ресурсы;
- браузер не тратит время на компиляцию шаблона.
Минусы:
- для работы нужен процесс сборки проекта;
- для компилирования шаблонов при сборке потребуется соответствующая технология (Node.js или нативная реализация на соответствующем языке программирования), что может быть накладно.
Заключение
Метод хранения сложных шаблонов в отдельных статических файлах мне кажется достаточно перспективным по сравнению с хранением тех же данных в HTML. С другой стороны, инициализация модуля оказывается гораздо сложнее, чем в случае с хранением шаблона в JS. Компилирование шаблона при сборке может стать идеальным вариантом, если в проекте есть сборка как таковая.
Коментарии к заметке
Ты забыл самый очевидный (на мой взгляд способ) — хранить шаблоны в отдельных HTML-файлах, а в браузер отдавать JS с уже скомпилированными шаблонами (в одном файле). Для Гранта уже есть такие задачи (поиск по слову JST) для нескольких шаблонизаторов, и совсем несложно сделать для других.
Вообще, это совсем не очевидный способ с точки зрения разработки, мне кажется, но вполне годный. Ребята в твиттере мне тоже подсказали его. Обязательно добавлю в основную статью.
Что касается лично меня, то я стараюсь избегать процесса сборки, если есть другие варианты.
Ну, да, год назад он был для меня тоже неочевиден :-) Но сейчас он кажется мне самым простым в использовании — ты получаешь каждый шаблон в отдельном и, главное, обычном HTML-файле. И тебе не нужно думать экранировании, предварительной компиляции и т. п.
А сборку не нужно бояться, особенно, когда она делает жизнь проще :-)
Полностью согласен с Артёмом, для больших проектов очень критично чтобы все было отдельно — шаблоны-клиент, шаблоны-сервер, лэйауты. Не очень силен в nodejs, знаю только что там есть проблемы со сборками шаблонов в grunt, но такие же аналоги на руби (ejs, jade, handlebars_assets) собирают все отлично, и все получается гораздо чище чем с text/x-template или тем более в варианте с отдельной подгрузкой. Правда, я находил несколько отличий у одинаковых компиляторов на js и на руби, но они совсем не критичны.
Это ты имеешь в виду, когда разметку нужно генерировать как на сервере (начальное состояние страницы), так и на клиенте (после действий пользователя) из одних и тех же шаблонов?
Нет, просто когда есть разные шаблоны сервер/клиент для разных страниц, или одна большая в разных кусках. В твоем случае, когда в одном куске нужна и клиентская логика и серверная (какой-то очень редкий случай), я бы сделал клиентский шаблон, а с сервера данные брал какой-нито АПИшкой.