1. Введение
Шаблонизация (templating) — метод связывания данных и разметки. Удобный способ генерации HTML по шаблону и данным. Используется на клиенте и сервере.
Суть шаблонизации заключается в том, чтобы отделить описание HTML от логики. Разметка помещается в отдельные файлы (шаблоны), а в местах, где необходимо вывести данные, размещаются специальные псевдопеременные. JS-код загружает нужный шаблон и заменяет в нем псевдопеременные на соответствующие данные.
На обычных сайтах, где HTML и CSS уже есть, элементы интерфейса получают готовый DOM и используя JavaScript вешают обработчики, оживляя его. Но в сложных интерфейсах разметка изначально отсутствует на странице. DOM создается с помощью JavaScript-кода динамически на основе данных, полученных с сервера или из других источников.
Представьте список постов на веб-странице, а так же любые другие ситуации, которые требуют отображения коллекции однотипных элементов, но с разными данными.
Данные для постов приходят от сервера как массив объектов, у нас есть шаблон одного поста. Используя цикл, мы можем пройтись по массиву объектов и вызвать функцию-шаблон для каждого объекта, результатом будет строка с подставленными данными. После чего мы в контейнер для постов просто повесим строку и браузер создаст разметку.
Это - стандартный подход написания динамических элементов интерфейса, данные для которых изменяются со временем.
1.1. Использование шаблонизации
Весь процесс требует нескольких простых шагов:
- Наличие данных, которыми будет наполнятся элемент интерфейса
- Шаблон, по которому будет составлена разметка элемента
- Библиотека, которая предоставляет средства шаблонизации

Мы подробно рассмотрим это на примерах, но по сути это очень просто:
- Подключить в проект выбранную библиотеку для шаблонизации
- Составить HTML-шаблон необходимого вида
- Сделать выборку шаблона в JS-файле
- Произвести рендеринг шаблона вместе с данными
2. Шаблон
Шаблон — это строка в специальном формате, которая путём подстановки значений и выполнения встроенных фрагментов кода превращается в HTML.
Синтаксис шаблона зависит от библиотеки, самое важное - это понимать принцип работы шаблонизаторов. Мы будем использовать библиотеку Handlebars.
2.1. Синтаксис
Шаблон - это строка со специальными разделителями, которых в Handlebars всего три:
<!--
Выражение expr между разделителями будет выполнено как есть,
то есть вместо имени переменной будет подставлено ее значение,
при этом если переменная содержит строку с тегами, то не произойдет
парса строки.
-->
<div>{{expr}}</div>
<!--
Используется для вставки HTML-тегов, то есть выражение будет
распарсеной браузером на наличие тегов.
-->
<div>{{{expr}}</div>
<!--
Используется для встроенных и кастомных операций, к примеру #each
работает как forEach, перебирая коллекцию, а в теле each к
элементу коллекции можно обратиться как this. А #if можно использовать
для ветвлений.
-->
<div>{{#helper}}{{/helper}}</div>
То есть синтаксис Handlebars - это обычный HTML, с вставками вида {{...}}.
Именно в те места, где указаны вставки, будут помещены данные.
<div class="menu">
<h3 class="menu-title">{{title}}</h3>
<ul class="menu-list">
{{#each items}}
<li class="menu-item">{{this}}</li>
{{/each}}
</ul>
</div>
2.2. Методы хранения
Шаблон - это просто многострочный HTML-текст, ему не место в файле скриптов.
Один из способов — записать его в HTML-файле в тег template.
Более современный способ, например при использовании
Webpack, хранить шаблон в отдельном файле и импортировать его. Для этого в
конфигурацию Webpack нужно добавить
загрузчик Handlebars-шаблонов.
Внешние шаблоны имеют много преимуществ, главным образом в том, что шаблоны никогда не будут загружаться клиенту, если они не нужны на странице. Для усвоения ключевых концепций будем пользоваться встроенными шаблонами.
Давайте обернем шаблон в тег template и дадим ему уникальный идентификатор,
чтобы можно было выбрать в JS-файле по селектору.
<template id="menu-template">
<div class="menu">
<h3 class="menu-title">{{title}}</h3>
<ul class="menu-list">
{{#each items}}
<li class="menu-item">{{this}}</li>
{{/each}}
</ul>
</div>
</template>
3. Использование шаблона
Вернемся к перечисленным шагам, где мы описали последовательность действий, необходимых для использования шаблонизации, и разберем по пунктам.
3.1. Добавить в проект библиотеку
Подключение библиотеки в проект происходит очень просто. Есть несколько вариантов.
- Скачать файл библиотеки и подключить его в
index.html - Использовать CDN-сервис для получения ссылки на файл библиотеки
- Если используется инструмент вроде
Webpack, можно ставить библиотеку как npm-пакет
Пока что используем CDN. В разделе
документации о установке
библиотеки есть ссылка на CDN-сервис на котором можно скопировать необходимый
URL. Это - минифицированая версия библиотеки. Все что нужно сделать — это
добавить еще один тег script перед нашим файлом скриптов и перед закрывающим
тегом body в index.html.
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.11/handlebars.min.js"></script>
3.2. Составить HTML шаблон
Этот шаг мы выполнили выше и у нас уже есть полностью готовый шаблон для меню. Добавим его перед всеми скриптами в документе.
<body>
<!-- html разметка -->
<template id="menu-template">
<div class="menu">
<h3 class="menu-title">{{title}}</h3>
<ul class="menu-list">
{{#each items}}
<li class="menu-item">{{this}}</li>
{{/each}}
</ul>
</div>
</template>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.11/handlebars.min.js"></script>
<script src="js/scripts.js"></script>
</body>
3.3. Сделать выборку шаблона в скрипте
template - это тег, значит мы можем выбрать его по селектору
тега/класса/идентификатора. Хорошей практикой считается давать тегам template,
содержащим шаблон, идентификаторы, так как они уникальны.
Теперь в JS-файле мы можем по id выбрать сам тег template и его контент в
виде строки, для этого используем свойство innerHTML.
const source = document.querySelector('#menu-template').innerHTML.trim();
3.4. Отрендерить шаблон с данными
У нас уже есть библиотека и шаблон из которого мы изъяли текстовый контент. Для
работы с шаблоном в библиотеке Handlebars есть функция compile. Эта функция
запускает компиляцию шаблона source и возвращает результат в виде функции,
которую далее можно запустить с данными и получить строку-результат.
Handlebars.compile(source)
Вызов Handlebars.compile(source) разбивает HTML-строку по разделителям и при
помощи new Function создаёт на её основе функцию. Тело этой функции создаётся
таким образом, что код, который в шаблоне оформлен как {{...}}, попадает в неё
как есть, а переменные и текст прибавляются к специальному временному буферу,
который в итоге возвращается.
const source = document.querySelector('#menu-template').innerHTML.trim();
const template = Handlebars.compile(source);

Теперь используя функцию-шаблон template можем передать ей данные как аргумент
и она вернет HTML-строку.
Для начала добавим данные для списка. В шаблоне указаны какие-то переменные
title и items.
В реальных задачах сначала создаются форматы для данных, а потом под них пишутся шаблоны, но для наглядности мы для шаблона напишем данные.
Данные для шаблона - это что угодно, строка, объект, массив и т.д., зависит от задачи, чаще всего - объект или массив объектов. Так как у нас список с заголовком и набором пунктов, нам удобно использовать объект такого вида.
const menuData = {
title: 'Eat it createElement, templates rule!',
items: ['Handlebars', 'LoDash', 'Pug', 'EJS', 'lit-html'],
};
Следующим шагом будет вызвать функцию-шаблон и передать ей menuData как
аргумент. В результате получим строку с подставленными значениями, поместим ее в
тег и браузер, распарсив ее, создаст HTML-разметку.
4. Шаблоны и Webpack
Когда используем сборщик модулей, очень удобно работать со внешними шаблонами.
Добавляем библиотеку.
npm install handlebars
Добавляем загрузчик handlebars-loader.
npm install --save-dev handlebars-loader
Обновляем конфигурацию Webpack, добавляя настройки загрузчика.
// В webpack.config.js
{
...
module: {
rules: [
...
{ test: /\.hbs$/, exclude: /node_modules/, use: "handlebars-loader" }
]
}
}
В папке src создаем папку templates, в которой добавляем файлы шаблонов с
расширением .hbs. Для нашего меню это будет menu.hbs. Помещаем разметку
шаблона в файл, без тега template.
<!-- В menu.hbs -->
<div class="menu">
<h3 class="menu-title">{{title}}</h3>
<ul class="menu-list">
{{#each items}}
<li class="menu-item">{{this}}</li>
{{/each}}
</ul>
</div>
Там, где хотим использовать шаблон, импортируем файл с шаблоном. Особенность в
том, что при импорте, handlebars-loader обработает файл шаблона и в
menuTemplate уже будет лежать скомпилированная функция-шаблон готовая к
использованию.
// В app.js
import menuTemplate from '/path/to/templates/menu.hbs';
const menuData = {
title: 'Eat it createElement, templates rule!',
items: ['Handlebars', 'LoDash', 'Pug', 'EJS', 'lit-html'],
};
const markup = menuTemplate(menuData); // html разметка с подставленным значениями