Использование шаблонов значительно упрощает создание HTML-страниц на стороне сервера. Но у этого подхода есть и недостатки. Он порождает определенные неудобства, связанные с повторяющимися блоками. В этом уроке мы поговорим об этой проблеме и выясним, как предотвращать такие ситуации.
Инвертированный подход к повторяющимся блокам
Представьте себе типовой сайт. На большинстве сайтов есть верхнее меню, футер и еще немало повторяющихся блоков. Как с ними правильно работать? Кажется, что копировать все это в каждый шаблон — не самая лучшая идея.
Есть еще один подход — вынести общие куски в отдельные файлы и включить их в нужных шаблонах:
<!-- Пример шаблонизатора JSP -->
<%@ include file="parts/header.jsp" %>
<!-- Здесь код конкретного шаблона -->
<%@ include file="parts/footer.jsp" %>
Это простое решение, которые использовалось в самых ранних шаблонизаторах. Разработчики быстро поняли, насколько это неудобно. Проблема заключается в двух аспектах:
- Разработчику приходится делать подобные включения абсолютно в каждом шаблоне вручную. В нашем примере их всего два. А что, если бы их были сотни? На работу с каждым отдельным шаблоном ушло бы очень много времени
- Разработчику приходится вручную проверять разные файлы и искать незакрытые теги, потому что части одних и тех же тегов могут быть разбросаны по разным файлам. Отлаживать такие проекты очень сложно
Эти проблемы решил инвертированный подход, в котором не куски сайта вставляются в шаблон, а шаблон вставляется в макет сайта. Принцип здесь такой:
- В одном месте описываем макет – основу сайта, которая не меняется по структуре
- Шаблонизатор делает так, чтобы шаблон конкретной страницы вставлялся в этот макет
В этом случае шаблон ничего не знает об устройстве макета, а макет хранит все свои части в одном месте.
Инвертированный подход в PUG
Шаблонизатор PUG поддерживает работу с макетами. Посмотрим, как это работает. Для начала создадим макет src/views/layouts/page.pug:
//- src/views/layouts/page.pug
html
head
meta(charset='utf-8')
meta(name='viewport', content='width=device-width, initial-scale=1')
body
p
a(href='/courses')
block content
Обратим внимание на content
— сюда PUG поместит шаблон конкретной страницы. Посмотрим, как выглядит шаблон списка курсов:
//- src/views/courses/index.pug
extends ../layouts/page.pug
block content
a(href='/courses/new') New Course
each course in courses
div
#{course.title}
С помощью операции extends
подключается макет в нужную страницу. В примере выше мы указали путь к шаблону макета относительно текущего шаблона.
Главная особенность в том, что мы помещаем содержимое шаблона в специальную конструкцию, которая вставляет его в нужное место в макет:
block content
Может показаться, что это избыточно. С другой стороны, создатели шаблонизатора хотели сделать чуть более продвинутую систему, чем подстановку в один блок. Такая конструкция позволяет вставлять в макет сразу несколько блоков и передавать туда данные конкретного шаблона. Посмотрим на пример такого макета и шаблона:
//- page.pug
html
head
title My Site
body
block content
block footer
Кроме content
, в этом макете используется footer
:
extends page.pug
block content
p Welcome
block footer
p Thanks for visiting, come again soon!
Код приложения будет выглядеть так:
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 } })
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)
})
app.get('/courses', (req, res) => {
const data = {
courses: state.courses,
}
res.view('src/views/courses/index', data)
})
app.listen({ port }, () => {
console.log(`Example app listening on port ${port}`)
})
Структура файлов шаблонов с макетом выглядит так:
views
├── layouts
│ └── page.pug
├── courses
│ ├── edit.pug
│ ├── index.pug
│ ├── new.pug
│ └── show.pug
└── index.pug
Самостоятельная работа
- Сделайте макет сайта. Создайте в нем верхнее меню, которое будет содержать заголовок и ссылку на главную страницу сайта. Добавьте футер со ссылкой на ваш профиль в GitHub
- Реорганизуйте шаблоны в вашем приложении так, чтобы они использовали макет
- Убедитесь на практике в преимуществе макетов. Попробуйте вносить различные изменения в макет и проверьте, что изменения отражаются сразу на всех страницах приложения
- Отправьте изменения на GitHub
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.