Pug - шаблонизатор и препроцессор HTML
Знакомимся с Pug - синтаксис и базовые возможности
В предыдущей статье мы установили и запустили Gulp
Теперь разберем первый инструмент для ускорения процесса верстки - Pug
Что такое Pug?
Pug - шаблонизатор и препроцессор HTML
Pug как препроцессор HTML - имеет свой синтаксис и может быть сгенерирован в HTML
Pug как шаблонизатор - может хранить фрагменты верстки в отдельных файлах и подключать их при необходимости в шаблон (примеры рассмотрим ниже)
Синтаксис Pug
Отличия от синтаксиса HTML
- Вложенность реализуется отступами
- Не нужны закрывающие теги
- Нет треугольных скобок
Для сравнения: слева синтаксис Pug, справа синтаксис HTML
Рассмотрим синтаксис пошагово - редактируем index.pug и смотрим генерируемый index.html
Указываем Doctype
doctype html
<!DOCTYPE html>
Добавляем корневой элемент html. Пока никакой вложенности нет, поэтому без отступов. Обратите внимание, что атрибуты тегов записываются в круглых скобках. Закрывающие теги не прописываются. Они будут автоматически сгенегированы
doctype html
html(lang="en")
<!DOCTYPE html>
<html lang="en"></html>
Добавляем два основных элемента HTML-страницы - head и body. Так как они должны быть вложены в корневой элемент html, то в строке перед head ставим один отступ Tab и в строке перед body ставим один отступ Tab. Это значит что элементы head и body будут находится внутри корневого элемента html. Опять же никаких закрывающих тегов мы не прописываем
doctype html
html(lang="en")
head
body
<!DOCTYPE html>
<html lang="en">
<head></head>
<body></body>
</html>
В head добавляем мета-тег кодировки документа. Атрибуты записываем в круглых скобках. Так как мета-тег кодировки должен быть внутри элемента head, то у мета-тега должен быть на один отступ Tab больше, чем у элемента head
doctype html
html(lang="en")
head
meta(charset="UTF-8")
body
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body></body>
</html>
Аналогично добавляем мета-тег для адаптивности. Если у тега несколько атрибутов, то записываем их через пробел или через запятую. Так как второй мета-тег должен быть также внутри элемента head, то у второго мета-тега также должен быть на один отступ Tab больше, чем у элемента head
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
body
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
</head>
<body></body>
</html>
Также в head добавляем тег title для названия страницы и теги link для подключения файлов стилей
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Pug</title>
<link rel="stylesheet" href="css/bootstrap-reboot.min.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body></body>
</html>
Добавим первый элемент в body - тег header. У header на один отступ Tab больше, чем у body, так как он должен быть расположен внутри body
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
header
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Pug</title>
<link rel="stylesheet" href="css/bootstrap-reboot.min.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<header></header>
</body>
</html>
Чтобы добавить класс для header, добавляем название класса через точку после названия тега, аналогично селекторам в файлах стилей CSS
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
header.header
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Pug</title>
<link rel="stylesheet" href="css/bootstrap-reboot.min.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<header class="header"></header>
</body>
</html>
Далее добавляем div с классом container. В Pug если не указывать название тега перед названием класса, то по-умолчанию назначается тег div. Поэтому вместо записи div.container
, мы просто указываем название класса как в файлах стилей CSS - .container
. Так как container внутри header, то у container на один отступ Tab больше, чем у header
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
header.header
.container
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Pug</title>
<link rel="stylesheet" href="css/bootstrap-reboot.min.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<header class="header">
<div class="container"></div>
</header>
</body>
</html>
Далее все аналогично - добавим header__flex внутри container, добавим header__logo внутри header__flex.
Теперь мы хотим добавить второй класс для header__logo - добавляем его через точку без пробелов - .header__logo.logo
Если захотим для header__logo добавить еще идентификатор, то запись будет следующей - .header__logo.logo#logo
- порядок не имеет значения, так что запись может быть и такой - #logo.header__logo.logo
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
header.header
.container
.header__flex
.header__logo.logo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Pug</title>
<link rel="stylesheet" href="css/bootstrap-reboot.min.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header__flex">
<div class="header__logo logo"></div>
</div>
</div>
</header>
</body>
</html>
Добавим logo__title c текстом. Текст записывается через пробел после названия тега или тега с классом
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
header.header
.container
.header__flex
.header__logo.logo
.logo__title Pug
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Pug</title>
<link rel="stylesheet" href="css/bootstrap-reboot.min.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header__flex">
<div class="header__logo logo">
<div class="logo__title">Pug</div>
</div>
</div>
</div>
</header>
</body>
</html>
Добавим в header__flex еще тег nav c классом header__nav и внутри него список ul c элементами списка li, которые в свою очередь содержать ссылки a c текстом.
Каждый дочерний элемент должен иметь на один отступ Tab больше, чем родительский. Но есть еще более краткая запись, через двоеточие, которую лучше использовать только для элементов, которые содержат в себе только один дочерний элемент, например li: a(href="") Home
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
header.header
.container
.header__flex
.header__logo.logo
.logo__title Pug
nav.header__nav
ul
li: a(href="") Home
li: a(href="") Portfolio
li: a(href="") About
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Pug</title>
<link rel="stylesheet" href="css/bootstrap-reboot.min.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<header class="header">
<div class="container">
<div class="header__flex">
<div class="header__logo logo">
<div class="logo__title">Pug</div>
</div>
<nav class="header__nav">
<ul>
<li><a href="">Home</a></li>
<li><a href="">Portfolio</a></li>
<li><a href="">About</a></li>
</ul>
</nav>
</div>
</div>
</header>
</body>
</html>
Первое время рекомендую самостоятельно, пошагово, добавляя по одному элементу в index.pug, смотреть на сгенерированный index.html, чтобы для себя более подробно понять, как из синтаксиса Pug генерируется разметка HTML
Нюансы синтакcиса Pug
Частая задача - в блоке с текстом необходимо определенную часть текста выделить в теги span (strong, i и другие) или сделать ссылкой a
В Pug есть два способа это сделать.
Первый способ - использовать обычные теги HTML - открывающий и закрывающий.
p Lorem ipsum dolor sit amet, consectetur <strong>adipisicing</strong> elit. Praesentium ad, ab accusamus. <a href="">Minima delectus</a> alias atque a, tenetur perspiciatis reiciendis eligendi quaerat. <br>Quos pariatur, fugit enim quod eaque, rerum.
Второй способ - использовать синтаксис Pug - #[tagname text]
- знак решётки, затем в квадратных скобках на первом месте имя тега, на втором месте через пробел текст, который будет находится в теге
p Lorem ipsum dolor sit amet, consectetur #[strong adipisicing] elit. Praesentium ad, ab accusamus. #[a(href="") Minima delectus] alias atque a, tenetur perspiciatis reiciendis eligendi quaerat. #[br]Quos pariatur, fugit enim quod eaque, rerum.
Javascript файлы подключаем, также как и остальные теги
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
script(src="js/jquery-3.5.1.min.js")
script(src="js/main.js")
Но если необходимо добавить javascript код прямо на странице, то используем следующий синтаксис - script.
- добавляем точку после имени тега script и на следующей строке с отступом на один больше, чем у тега script пишем необходимый код
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
script(src="js/jquery-3.5.1.min.js")
script(src="js/main.js")
script.
console.log('1')
Шаблонизатор
Когда верстаем многостраничный сайт, при внесении изменений, например в header, необходимо открыть каждый *.html файл, который содержит header и внести эти изменения в каждом файле.
Чтобы решить данную проблему, выделим общий фрагмент кода в отдельный файл и подключим его в шаблон
Предположим, у нас есть макет сайта с тремя страницами, где есть общие блоки header и footer, а блок main будет на каждой странице отличаться.
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
header.header
.container
.header__flex
.header__logo.logo
.logo__title Pug
nav.header__nav
ul
li: a(href="") Home
li: a(href="") Portfolio
li: a(href="") About
main.main
.container
.main__title Index Page
.main__text
p Lorem ipsum dolor sit amet, consectetur #[strong adipisicing] elit. Praesentium ad, ab accusamus. #[a(href="") Minima delectus] alias atque a, tenetur perspiciatis reiciendis eligendi quaerat. #[br]Quos pariatur, fugit enim quod eaque, rerum.
footer.footer
.container
.footer__copyright © Pug 2020
Когда сверстали главную страницу, сохраним блоки header и footer в соответствующие файлы header.pug и footer.pug в отдельную папку includes (создаем там же, где лежит основной файл index.pug)
Отдельная папка includes для общих фрагментов верстки нужна для того, чтобы они не генерировались как отдельные html файлы, а только подключались в шаблоны страниц. Если сохраним их рядом с index.pug, то они будут сгенерированы, как отдельные страницы header.html и footer.html, а это некорректно.
Рядом с файлом index.pug сохраняем только те файлы, которые должны быть сгенерированы, как отдельные страницы. Об этом чуть ниже
header.pug
header.header
.container
.header__flex
.header__logo.logo
.logo__title Pug
nav.header__nav
ul
li: a(href="") Home
li: a(href="") Portfolio
li: a(href="") About
footer.pug
footer.footer
.container
.footer__copyright © Pug 2020
Обратите внимание, что в отдельных файлах header и footer отступы сбрасываются - основные родительские элементы header.header
и footer.footer
в отдельных файлах не имеют отступа. Это важно при подключении этих файлов в шаблоны.
Подключим эти файлы в index.pug. Для этого используем служебное слово include
.
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
include includes/header
main.main
.container
.main__title Index Page
.main__text
p Lorem ipsum dolor sit amet, consectetur #[strong adipisicing] elit. Praesentium ad, ab accusamus. #[a(href="") Minima delectus] alias atque a, tenetur perspiciatis reiciendis eligendi quaerat. #[br]Quos pariatur, fugit enim quod eaque, rerum.
include includes/footer
Так как отступы в отдельных файлах header.pug и footer.pug сброшены, то при подключении header.pug через служебное слово include
следим, чтобы подключение было именно в том месте, где изначально был header.header
, то есть на один отступ Tab больше, чем у body. Тоже самое и с footer.pug
Теперь на основе шаблона index.pug, в этой же папке создадим еще два шаблона - portfolio.pug и about.pug
portfolio.pug
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
include includes/header
main.main
.container
.main__title Portfolio Page
.main__portfolio
include includes/footer
about.pug
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
title Pug
link(rel="stylesheet", href="css/bootstrap-reboot.min.css")
link(rel="stylesheet", href="css/main.css")
body
include includes/header
main.main
.container
.main__title About Page
.main__about Lorem ipsum dolor sit amet, consectetur adipisicing elit. Fuga laudantium labore eos, nobis accusantium? Totam rem culpa fuga saepe facere.
include includes/footer
Теперь генерируются 3 страницы - index.html, portfolio.html и about.html
При изменении header.pug или footer.pug, эти изменения будут применены сразу на всех трёх страницах
В простом примере польза шаблонизации не ощущается в полной мере, но когда реальный проект состоит более чем из 10 страниц и более 20 общих фрагментов, в которые так или иначе в процессе верстки вносятся изменения, то польза становится намного ощутимей
Возможные проблемы и их решения
В окне командной строки, где запущен Gulp, ошибки выглядят следующим образом
На скриншоте выше выделена наиболее информативная часть, где можно видеть код ошибки (code), информация об ошибке (msg), строка с ошибкой (line), файл с ошибкой (filename)
Основная ошибка - это использование в отступах Пробелы вместо Tab. В документации сказано, что в качестве отступов можно использовать или только Пробелы или только Tab.
Решить эту проблему очень просто. В редакторе кода, в файле index.pug выделяем весь код сочетанием клавиш Ctrl+A и ищем настройку Tab size (в Sublime Text и VS Code справа внизу), нажимаем и выбираем пункт Convert Indentation to Tabs - все отступы конвентируются в Tab
Преимущества верстки с Pug
У Pug много возможностей. Мы разобрали только базовые для ускорения и упрощения процесса верстки, но можем выделить следующие преимущества
-
Краткий, информативный, чистый синтаксис, не нужно следить за закрывающими тегами
-
Разбивка верстки на отдельные фрагменты, подключение этих фрагментов в шаблоны, и изменения в общем фрагменте применяются сразу на всех страницах, где он подключен
-
Сгенерированна верстка является валидной (проверка W3C - Markup Validation Service)
В следующей статье познакомимся и разберем работу со вторым очень полезным инструментом для ускорения верстки и более комфортной работы со стилями - Stylus (препроцессор CSS)
Полезные ссылки
Официальный сайт Pug
Страница плагина gulp-pug