Если проанализировать http
запросы к типичному сайту, то можно заметить, что большая часть этих запросов направлена на получение контента, а не его модификацию. Другими словами, основная работа обработчиков состоит в том, чтобы сформировать правильный html
и отправить его клиенту (браузеру). Единственный способ для генерации html
, с которым мы знакомы, это ручной сбор строчки, содержащей разметку, и отправка посредством метода send
.
app.get('/', (req, res) => {
res.send('<div>Hello World!</div>');
});
Сказать, что этот способ плох, это ничего не сказать. Кроме того, что это крайне неудобно, существует масса других недостатков в таком подходе. Если заглянуть в историю развития web, то выяснится интересный факт: php
появился как средство решения описанной выше задачи, а не как язык программирования.
Задачу по формированию разметки называют шаблонизацией, а конкретные библиотеки для шаблонизации называют шаблонизаторами. Общий принцип работы такой: описываются файлы с разметкой, а библиотека предоставляет функции для загрузки этих шаблонов в код. Во время загрузки происходят необходимые подстановки и шаблон заполняется конкретными данными.
Jinja Like
Классическим примером может служить шаблонизатор jinja
из мира питона. Его популярность привела к тому, что в каждом языке есть множество шаблонизаторов, очень похожих и даже работающих так же, как jinja
. Поэтому можно говорить о целом классе jinja-like
шаблонизаторов.
<h1 class="header">{{ pagename | title }}</h1>
<div class="small">authors</div>
<ul>
{% for author in authors %}
<li{% if loop.first %} class="first"{% endif %}>
{{ author }}
</li>
{% endfor %}
</ul>
По сути jinja
— это хоть и примитивный, но полноценный язык программирования, который вкрапливается в файл с разметкой и расширяет его во время обработки. Несмотря на очевидность этого решения, оно обладает рядом недостатков. Первое — это сложность редактирования такого рода шаблонов. Из-за перемешивания кода с версткой приходится скакать вверх-вниз чтобы добавить/удалить/изменить теги, и то же самое нужно делать с конструкциями самого языка. Этот недостаток может быть не очевиден тем, кто никогда не видел альтернативных решений, и как мы увидим позже, они есть. Второе: в подобных шаблонизаторах текст вне конструкций шаблонизатора, то есть та самая вёрстка, никак не анализируется. Это легко приводит к проблемам типа "незакрытый тег", или семантическому нарушению html
, когда неправильно друг в друга вкладываются теги, используются несуществующие атрибуты и тому подобное. И третий немаловажный момент: оформление шаблонов не проверяется автоматическими инструментами, и поэтому стиль будет сильно зависеть от человека.
Pug (Haml Like)
Существует и совершенно другой подход к организации шаблонов. Когда я в первый раз увидел такое, то был немало удивлен. Кажется, что самостоятельно дойти до этого решения очень сложно. Чтобы не томить, сразу покажу пример:
h1.header= pagename
.small authors
ul
each author, index in authors
li(class= index === 0 && "first")= author
Этот пример почти идентичен тому, что было выше с использованием 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);
})
// index.pug
html
head
title= title
body
h1= message
Всё сводится к установке зависимости и установке pug
в качестве движка для рендеринга шаблонов. После этого, внутри обработчиков можно начинать использовать метод render
. Первый параметр которого - это путь до шаблона, второй - набор параметров для подстановок внутри шаблона.
Это не единственный способ передачи параметров в шаблон. В большинстве случаев они передаются именно вторым параметром в render
, но иногда возможны ситуации, в которых у нас есть сквозная функциональность, и было бы крайне неудобно прокидывать их в шаблон в каждом обработчике. Реализуется это через установку свойств в объект res.locals
, а в шаблоне эти свойства становятся доступны как переменные. Эту особенность мы будем использовать позже, когда начнем работать с сессиями и аутентификацией. Помните, что злоупотреблять этим способом не стоит, явное лучше неявного. Стремитесь к тому, чтобы код был чистый (использовал чистые функции).
Наследование шаблонов
На практике сайт не всегда состоит из уникальных страниц. Обычно меняется только контентная часть, а вокруг одно и тоже. Часть, которая не меняется, принято называть макетом или лейаутом (layout). Это настолько распространенный кейс, что большинство шаблонизаторов поддерживают механизм для выделения лейаутов. В pug
он называется наследованием шаблонов. Ниже приведён пример такого наследования.
//- layout.pug
html
head
title My Site - #{title}
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
, блок показывается по определённому условию, то вы не сможете этого избежать, единственное о чем нужно помнить, это создавать вовремя правильные абстракции (функции) для избежания дублирования, а так же для выделения бизнес-правил.
Перезагрузка кода
В отличие от js кода, Express автоматически перечитывает файлы с шаблонами после каждого запроса, другими словами вам не требуется помощь nodemon
для рестарта приложения при обновлении шаблонов.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты