- Зачем нужны шаблонизаторы
- Как начать работать с PUG
- Как работает отображение данных
- Какие управляющие конструкции используются в Pug
В этом уроке мы познакомимся с шаблонизаторами.
Зачем нужны шаблонизаторы
Fastify и другие подобные фреймворки позволяют создавать полноценные сайты без дополнительных инструментов. Рабочий сайт должен просто возвращать ответ с HTML-кодом, который браузер отобразит как страницу:
app.get('/', (req, res) => {
res.type('html')
res.send('<h1>Hello World!</h1>')
})
Здесь мы видим упрощенный пример, в котором мы возвращаем только заголовок H1.
В реальных приложениях возвращаемый HTML состоит из сотен и тысяч строк. Работать с такими объемами стандартным способом очень сложно. Вот лишь некоторые проблемы, с которыми мы столкнемся:
- Такой код сложно формировать, редактировать и поддерживать
- В таком коде очень легко допустить ошибку и очень сложно ее обнаружить
- В таком коде будут возникать проблемы с одинарными или двойными кавычками, придется их экранировать и постоянно следить за этим
Для решения этой проблемы используются шаблонизаторы. Это библиотеки, которые позволяют формировать HTML в отдельных файлах с подсветкой и удобной подстановкой данных. Другими словами, мы получаем не HTML внутри кода, а код внутри HTML.
В мире Fastify есть несколько разных шаблонизаторов. Для этого курса мы выбрали Pug.
Посмотрим, как выглядит шаблон в PUG:
doctype html
html(lang='en')
head
title Hello, World!
body
h1 Hello, World!
div.remark
p Pug rocks!
Как начать работать с PUG
Чтобы начать работу с pug в Fastify, нужно установить два пакета:
npm i pug @fastify/view
Пакет @fastify/view добавляет в Fastify поддержку шаблонизаторов. При подключении плагина нужно указать, что мы хотим использовать Pug в качестве шаблонизатора в нашем приложении:
import fastify from 'fastify'
import view from '@fastify/view'
import pug from 'pug'
const app = fastify()
const port = 3000
// Подключаем pug через плагин
await app.register(view, { engine: { pug } })
app.listen({ port }, () => {
console.log(`Example app listening on port ${port}`)
})
Шаблоны Pug хранятся в директории src/views и имеют расширение pug. Чтобы попрактиковаться, добавим первый шаблон для главной страницы сайта. Для этого выполним два действия:
Создадим шаблон src/views/index.pug со следующим содержимым:
doctype html html(lang='en') head title Hello Hexlet! body main h1 Hello, World! p Fastify + Pug
Укажем обработчику главной страницы использовать этот шаблон:
app.get('/', (req, res) => res.view('src/views/index'));
Обратите внимание на метод res.view()
в коде выше. Он выполняет рендеринг указанного шаблона и добавляет результат в HTTP-ответ.
Путь до шаблона указывается относительно директории корня приложения:
# Например, структура может выглядеть так
views
├── courses
│ ├── edit.pug
│ ├── index.pug
│ ├── new.pug
│ └── show.pug
├── index.pug
└── users
├── edit.pug
├── index.pug
└── new.pug
Сами шаблоны могут располагаться и на более глубоком уровне. Это становится важно, когда количество шаблонов увеличивается.
Шаблонизатор не задает правила именования и внутренней структуры шаблонов. Но работать без правил слишком сложно, поэтому со временем мы самостоятельно выработаем правила и будем их придерживаться.
Как работает отображение данных
Как правило, HTML внутри шаблонов формируется на основе данных, которые мы хотим вывести. Например, чтобы вывести информацию о курсе на странице /courses/:id, мы передаем объект этого курса в шаблон и формируем HTML на основе его содержимого.
Рассмотрим, по какому алгоритму обработчики обычно работают с шаблонами:
- Сначала они извлекают все необходимые данные
- Затем они создают объект с данными для шаблона
- В итоге они передают этот объект в шаблон в виде объекта
const state = {
courses: [
{
id: 1,
title: 'JS: Массивы',
description: 'Курс про массивы в JavaScript',
},
{
id: 2,
title: 'JS: Функции',
description: 'Курс про функции в JavaScript',
},
],
}
app.get('/courses/:id', (req, res) => {
const { id } = req.params
const course = state.courses.find(({ id: courseId }) => courseId === parseInt(id))
if (!course) {
res.code(404).send({ message: 'Course not found' })
return
}
const data = {
course,
}
res.view('src/views/courses/show', data)
})
Остался последний шаг — вывести данные курса в шаблоне. Для этого в шаблонах есть доступ к объекту, который мы передали в обработчике:
doctype html
html
head
title Hello Hexlet!
body
main
h1= course.title
p= course.description
Какие управляющие конструкции используются в Pug
Кроме обычного вывода значений, в шаблонах часто используются условные конструкции и циклы. С их помощью показываются списки, прячутся или показываются определенные блоки и так далее.
Подробнее обо всех этих конструкциях можно прочитать в официальной документации. А здесь мы разберем пример на базе маршрута /courses
, по которому выводится список курсов с описанием и ссылками на курсы.
Обработчик маршрута:
app.get('/courses', (req, res) => { const data = { courses: state.courses, // Где-то хранится список курсов header: 'Курсы по программированию', } res.view('src/views/courses/index', data) })
Шаблон
html head title Хекслет body h1= header if courses.length === 0 p Пока не добавлено ни одного курса else each course in courses div h2 a(href=`/courses/${course.id}`)= course.title p= course.description
Логика вывода здесь такая:
- Если список курсов пустой, то выводится соответствующее сообщение
- Если курсы в списке есть, то на основе этого списка формируются HTML-блоки с информацией о курсе и ссылки на его страницу
Этих конструкций хватит для решения большинства стандартных задач.
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.