Pug - шаблонизатор и препроцессор HTML

Знакомимся с Pug - синтаксис и базовые возможности

bg

В предыдущей статье мы установили и запустили Gulp

Теперь разберем первый инструмент для ускорения процесса верстки - Pug



Прежде чем начать, хотел бы обратить внимание, что у блога есть телеграм канал https://t.me/frontips, где можно узнавать о выходе новых статей и со временем будет появляться больше интересной и полезной информации.

Поддержите развитие блога и канала подпиской!

А теперь перейдём к теме статьи ;) Приятного чтения!



Что такое Pug?

Pug - шаблонизатор и препроцессор HTML

Pug как препроцессор HTML - имеет свой синтаксис и может быть сгенерирован в HTML

Pug как шаблонизатор - может хранить фрагменты верстки в отдельных файлах и подключать их при необходимости в шаблон (примеры рассмотрим ниже)



Синтаксис Pug


Отличия от синтаксиса HTML
  1. Вложенность реализуется отступами
  2. Не нужны закрывающие теги
  3. Нет треугольных скобок

Для сравнения: слева синтаксис Pug, справа синтаксис HTML

1


Рассмотрим синтаксис пошагово - редактируем 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)

4

5

Отдельная папка 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

6

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

7

При изменении header.pug или footer.pug, эти изменения будут применены сразу на всех трёх страницах

В простом примере польза шаблонизации не ощущается в полной мере, но когда реальный проект состоит более чем из 10 страниц и более 20 общих фрагментов, в которые так или иначе в процессе верстки вносятся изменения, то польза становится намного ощутимей



Возможные проблемы и их решения

В окне командной строки, где запущен Gulp, ошибки выглядят следующим образом

3

На скриншоте выше выделена наиболее информативная часть, где можно видеть код ошибки (code), информация об ошибке (msg), строка с ошибкой (line), файл с ошибкой (filename)

Основная ошибка - это использование в отступах Пробелы вместо Tab. В документации сказано, что в качестве отступов можно использовать или только Пробелы или только Tab.

Решить эту проблему очень просто. В редакторе кода, в файле index.pug выделяем весь код сочетанием клавиш Ctrl+A и ищем настройку Tab size (в Sublime Text и VS Code справа внизу), нажимаем и выбираем пункт Convert Indentation to Tabs - все отступы конвентируются в Tab



Преимущества верстки с Pug

У Pug много возможностей. Мы разобрали только базовые для ускорения и упрощения процесса верстки, но можем выделить следующие преимущества

  1. Краткий, информативный, чистый синтаксис, не нужно следить за закрывающими тегами

  2. Разбивка верстки на отдельные фрагменты, подключение этих фрагментов в шаблоны, и изменения в общем фрагменте применяются сразу на всех страницах, где он подключен

  3. Сгенерированна верстка является валидной (проверка W3C - Markup Validation Service)

2


В следующей статье познакомимся и разберем работу со вторым очень полезным инструментом для ускорения верстки и более комфортной работы со стилями - Stylus (препроцессор CSS)



Полезные ссылки

Официальный сайт Pug

Страница плагина gulp-pug



Друзья, стараюсь для вас, поддержите проект!

Подписывайтесь, впереди много всего интересного и полезного ;)

Telegram - https://t.me/frontips

VK - https://vk.com/frontips