Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Шаблоны JS: Express

Если проанализировать 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 .... В неё передаётся имя окружающего шаблона, который чаще является макетом. В макете определяется блок (или блоки), в которые будет происходить подстановка кусков шаблона. Далее необходимо в шаблоне (не макете) определить такие же блоки и наполнить их контентом. Синтаксис задания блоков в обоих местах одинаковый, только в одном случае блок не содержит тела, а в другом содержит.

Включения

Так же бывает полезным механизм включения, позволяющий выделять из шаблонов общие части и переиспользовать их.

includes

Чистота

На просторах интернета постоянно спорят о том, что может быть в шаблоне, а чего нет. Что является логикой вывода, а что нет. При этом есть ряд правил, которые объективно нарушать не стоит:

  1. Ни в коем случае шаблон не должен порождать побочных эффектов. В шаблоне нельзя писать в базу, изменять данные на диске и вообще любым способом пытаться влиять на окружающую среду. Шаблон это исключительно чтение.
  2. Шаблоны должны быть декларативны, никакого изменения состояний, другими словами, если вы начинаете внутри шаблона вводить переменные и изменять их, то, по сути, шаблон превращается в полноценный скрипт, который вы программируете. Не допускайте этого.
  3. Использовать логику, влияющую на вывод внутри шаблона — это нормально. Если у вас, с точки зрения ui, блок показывается по определённому условию, то вы не сможете этого избежать, единственное о чем нужно помнить, это создавать вовремя правильные абстракции (функции) для избежания дублирования, а так же для выделения бизнес-правил.

Перезагрузка кода

В отличие от js кода, Express автоматически перечитывает файлы с шаблонами после каждого запроса, другими словами вам не требуется помощь nodemon для рестарта приложения при обновлении шаблонов.


Дополнительные материалы

  1. Pug

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»