Препроцессор Pug / Jade

Препроцессор Pug изначально назывался Jade, поэтому теперь можно встретить разные названия с одним и тем же описанием.

Комментарии (Comments)

// однострочный комментарий, преобразуется в html
//- однострочный комментарий pug, не отображается в html

//-
    Блочный комментрарий.
    Не преобразуется в html.
//
    Блочный комментрарий.
    Преобразуется в html.

Условные комментарии

//[if lt IE 9]><html lang="en" class="lt-ie9"><![endif]
//[if lt IE 9]><script src="js/html5.js"></script><![endif]

Доктайп (Doctype)

//- doctype html
<!DOCTYPE html> 

//- doctype xml
<?xml version="1.0" encoding="utf-8" ?>

//- doctype transitional
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

//- doctype strict
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

//- doctype frameset
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">

//- doctype 1.1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

//- doctype basic
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">

//- doctype mobile
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">

//- doctype plist
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

//- собственный doctype
doctype html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"

При создании собственного доктайпа следует учесть что:

  • тип закрывающих скобок />, > зависит от указанной спецификации XML или HTML
  • если нет возможности использовать слово doctype, но указать его все же хочется, можно воспользоваться специальными опциями. Больше информации на оф сайте pug
    var pug = require('pug');
    
    var source = 'img(src="foo.png")';
    
    pug.render(source);
    // => '<img src="foo.png"/>'
    
    pug.render(source, {doctype: 'xml'});
    // => '<img src="foo.png"></img>'
    
    pug.render(source, {doctype: 'html'});
    // => '<img src="foo.png">'

Тэги (Tags)

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

ul            |   <ul>
  li Item A   |     <li>Item A</li>
  li Item B   |     <li>Item B</li>
  li Item C   |     <li>Item C</li>
              |   </ul>


//- pug знает большинство самозакрывающихся элементов,
//- но если не знает, можно добавить / 
img

svg
  use(xlink:href='img/icons.svg#logo')/

//- для сокращения кода в некоторых случаях можно использовать уплотняющую запись преобразования
a: img

Атрибуты (Attributes)

Для записи id и классов можно использовать короткую запись:

.block Текст            | <div class="block">Текст</div>
span.icon Текст         | <span class="icon">Текст</span>

#block Текст            | <div id="block">Текст</div>
p#chapter Текст         | <p id="chapter">Текст</p>

Атрибуты похожи на html, однако их значения — это обычный JavaScript. Поэтому, если вы вставите туда выражение JS, оно сработает.

a(href='google.com') Google
a(class='button' href='google.com') Google
a(class='button', href='google.com') Google

//- обработка js, присвоение класса в зависимости от переменной
- var authenticated = true
body(class=authenticated ? 'authed' : 'anonim')

//- запись в несколько строк
input(
  type='checkbox'
  name='agreement'
  checked
)

//- не стандартные атрибуты.
//- атрибуты со скобками указываются через , либо обрамляются кавычками
div(class='div-class', (click)='play()')
div(class='div-class' '(click)'='play()')

//- переменные в атрибутах
- var url = 'test.html',
  url_2 = 'https://example.com/'

a(href='/' + url) Текст ссылки
a(href=url_2) Вторая ссылка

//- устаревший синтаксис интерполяции
a(href="/#{url}") Текст ссылки

//- если есть поддержка ES2015
- var btnType = 'info'
- var btnSize = 'lg'

button(type='button' class='btn btn-' + btnType + ' btn-' + btnSize)
button(type='button' class=`btn btn-${btnType} btn-${btnSize}`)

//- чтобы символы не экранировались используется !=
div(escaped="<code>")
div(unescaped!="<code>")

//- стили можно записывать как привычной строкой, так и объектом
a(style="color: 'red', background: 'green'")
a(style={color: 'red', background: 'green'})

//- классы можно записывать как привычной строкой, так и массивом
- var classes = ['foo', 'bar', 'baz']
a(class=classes)

//- атрибут class может быть продублирован
a.bang(class=classes class=['bing'])

//- атрибут также может быть объектом, сопоставляющим значения true и false
- var currentUrl = '/about'
a(class={active: currentUrl === '/'} href='/') Home            | <a href="/">Home</a>
a(class={active: currentUrl === '/about'} href='/about') About | <a class="active" href="/about">About</a>

Логический тип данных

input(type='checkbox' checked)                  |  <input type="checkbox" checked="checked" />
input(type='checkbox' checked=true)             |  <input type="checkbox" checked="checked" />
input(type='checkbox' checked=false)            |  <input type="checkbox" />
input(type='checkbox' checked=true.toString())  |  <input type="checkbox" checked="true" />

//- если указан docktype html, pug использует краткую запись

doctype html                                    |  <!DOCTYPE html>

input(type='checkbox' checked)                  |  <input type="checkbox" checked>
input(type='checkbox' checked=true)             |  <input type="checkbox" checked>
input(type='checkbox' checked=false)            |  <input type="checkbox">
input(type='checkbox' checked=true && 'checked')|  <input type="checkbox" checked="checked">

Запись &attributes

Может использоваться для разбивания объекта с атрибутами на части. Символы при  этом не экранируются, если используются вне миксинов.

div.foo(data-bar="foo")&attributes({'data-foo': 'bar'})  |  <div class="foo" data-bar="foo" data-foo="bar"></div>

- var attributes = {};
- attributes.class = 'baz';
div.foo(data-bar="foo")&attributes(attributes)           |  <div class="foo baz" data-bar="foo"></div>

Case (Case)

Сокращенный вариант JS условия switch.

- var friends = 10
case friends
  when 0
    p you have no friends
  when 1
    p you have a friend
  default
    p you have #{friends} friends

//- как и в js можно пропускать значения
- var friends = 0
case friends
  when 0
  when 1
    p you have very few friends
  default
    p you have #{friends} friends

//- для прерывания условия стоит использовать break
- var friends = 0
case friends
  when 0
    - break
  when 1
    p you have very few friends
  default
    p you have #{friends} friends

//- запись в 1 строку
- var friends = 1
case friends
  when 0: p you have no friends
  when 1: p you have a friend
  default: p you have #{friends} friends

Код (Code)

Пуг позволяет обрабатывать JS код, разделяя его на 3 типа: буферизированный, небуферизированный, неэкранированный буферизированный.

Небуферизованный код ничего не добавляет к выводам и начинается с —

//- запись в строку
- for (var x = 0; x < 3; x++)
  li item

//- запись блоком
-
  var list = ["Uno", "Dos", "Tres",
          "Cuatro", "Cinco", "Seis"]
each item in list
  li= item

Буферизированный код начинается с =, он вычисляет JS и выводит результат. Для безопасности html символы экранируются.

p
  = 'This code is <escaped>!'

//- инлайновая запись
p= 'This code is' + ' <escaped>!'

Неэкранированный буферизированный код начинается с !=, он вычисляет JS и выводит результат, без какой-либо экранизации, что не безопасно при использования пользователями. 

p
  != 'This code is <strong>not</strong> escaped!'

//- инлайновая запись
p!= 'This code is' + ' <strong>not</strong> escaped!'

Условия (Conditionals)

Условия if, else if, else

- var user = { description: 'foo bar baz' }
- var authorised = false
#user
  if user.description
    h2.green Description
    p.description= user.description
  else if authorised
    h2.blue Description
    p.description.
      User has no description,
      why not add one...
  else
    h2.red Description
    p.description User has no description

с оператором сравнения: 

if var=='text'
   p #{text}

условие unless (отрицательный if)

unless user.isAnonymous
  p You're logged in as #{user.name}

Фильтры (Filters)

Фильтры позволяют использовать другие языки в шаблонах Pug. Все модули JSTransformer могут использоваться в качестве фильтров Pug. Популярные фильтры: :babel, :uglify-js, :scss и :markdown-it.

Можно использовать как уже существующие, так и собственные фильтры.

Для вставки опций в фильтры заключите их в скобки записав после вызова фильтра: :less(ieCompat=false).

Если вы хотите использовать CoffeeScript и Markdown (используя Markdown-it renderer) в шаблоне Pug, сначала убедитесь, что эти функции установлены:

$ npm install --save jstransformer-coffee-script
$ npm install --save jstransformer-markdown-it

Запись условия с фильтрами:

:markdown-it(linkify langPrefix='highlight-')
  # Markdown

  Markdown document with http://links.com and

  ```js
  var codeBlocks;
  ```
script
  :coffee-script
    console.log 'This is coffee script'

Фильтры отображаются во время компиляции. Это делает их быстрыми, но это также означает, что они не могут поддерживать динамический контент или параметры.

Инлайновая запись

p
  :markdown-it(inline) **BOLD TEXT**

p.
  In the midst of a large amount of plain
  text, suddenly a wild #[:markdown-it(inline) *Markdown*]
  appeared.

Использование вместе с include

//- index.pug
doctype html
html
  head
    title An Article
  body
    include:markdown-it article.md


# article.md
This is an article written in markdown.

Вложенные фильтры

Фильтры применяются в обратном порядке. 

script
  :cdata-js:babel(presets=['es2015'])
    const myFunc = () => `This is ES2015 in a CD${'ATA'}`;

Пользовательские фильтры

Cобственные фильтры добавляются через опцию filters.

options.filters = {
  'my-own-filter': function (text, options) {
    if (options.addStart) text = 'Start\n' + text;
    if (options.addEnd)   text = text + '\nEnd';
    return text;
  }
};



p
  :my-own-filter(addStart addEnd)
    Filter
    Body

Включения (Includes)

Includes позволяет включать содержимое одного файла в другой. Если подключать не pug файл, то в результат вставится содержимое файла.

//- includes/head.pug
head
  title My Site
  script(src='/javascripts/jquery.js')
  script(src='/javascripts/app.js')

//- includes/foot.pug
footer#footer
  p Copyright (c) foobar

//- index.pug
doctype html
html
  include includes/head.pug
  body
    h1 My Site
    p Welcome to my super lame site.
    include includes/foot.pug



//- включения можно комбинировать с фильтрами

//- index.pug
doctype html
html
  head
    title An Article
  body
    include:markdown-it article.md

//- article.md
# article.md
This is an article written in markdown.

Наследование (Inheritance: Extends and Block)

Pug поддерживает шаблонное наследование, реализуемое с помощью ключевых слов block и extends.

block — блоки кода, которые можно перезаписывать. В блок можно включить контент «по умолчанию», тогда он будет доступен на всех страницах к которым подключается, если его не перезапишут принудительно. 

Так выглядит объявление блоков:

//- layout.pug
html
  head
    title My Site - #{title}
    block scripts
      script(src='/jquery.js')
  body
    block content
    block foot
      #footer
        p some footer content

Команда extends добавляет данные из основного макета к новой странице.

Так выглядит дополнительный файл к которому подключается основная разметка блоков. Блок scripts перезаписывается, блок content заполняется данными, блок foot остается неизменным:

//- page-a.pug
extends layout.pug

block scripts
  script(src='/jquery.js')
  script(src='/pets.js')

block content
  h1= title
  - var pets = ['cat', 'dog']
  each petName in pets
    include pet.pug


//- pet.pug
p= petName

Block append / prepend

Изменение и расширение данных в блоках.

block name — блок с именем. При размещении на другой странице текста в блоке — значение по умолчанию затрется.

block appends name — добавить к блоку. При размещении на другой странице значение прибавится к значению по умолчанию.

block prepends name — указывает что в этом блоке надо обрабатывать условия appends блока.

//- layout.pug

block vars
 - var text="Базовый текст"

block content
 p #{text}

 Пример изменения текста переменной в блоке

//- page.pug
extends layout.pug

block append vars
  - var text="Измененный текст"

block prepend content
 p #{text}

Интерполяция (Interpolation)

Строковая интерполяция, экранируемая

- var simpletext = "Все, что сказано три раза, становится истиной";
- var numeric = 42;
- var htmlstring = "<span>текст в html тегах</span>";

h1= simpletext 
p The Ultimate Question of Life, the Universe, and Everything: #{numeric}
p Html теги без обработки: #{htmlstring}
p Можно добавлять любые js выражения #{simpletext.toUpperCase()}
p Можно не экранировать фигурные скобки #{'}'}!
p Для отображения конструкции переменной можно использовать экранирование \#{interpolation}
p или заключить в конструкцию повторно #{'#{interpolation}'}

Строковая интерполяция, не экранируемая

p !{simpletext}

Интерполяция тегов

p. 
  В параграфы текста могут быть встроены интерполяторы тегов pug. Например:
  #[strong жирный текст] или
  #[em текст курсивный] и даже  
  #[q(title="¡Hola Mundo!") теги с атрибутами]

Интерполяция и пробелы

p
  | Если при написании параграфа использовать подобный вид интерполяции тегов
  strong жирный текст
  | или
  em курсив
  | слова будут склеиваться, т.к. пробелы удаляются.
p.
  Чтобы пробелы оставались на месте, используйте интерполяцию тегов pug
  #[strong respected] и #[em everybody] 

Итерации (Iteration)

Pug поддерживает основные методы итераций: each, for и while.

each

//- простейшая запись
ul
  each val in [1, 2, 3, 4, 5]
    li= val

//- вывод индекса и значения
ul
  each val, index in ['zero', 'one', 'two']
    li= index + ': ' + val

//- перебор объекта вместо массива
ul
  each val, index in {1:'one',2:'two',3:'three'}
    li= index + ': ' + val

//- в качестве массива может выступать любая js сущность
- var values = [];
ul
  each val in values.length ? values : ['There are no values']
    li= val

//- можно добавить блок else, выводящийся если у массивов или объектов нет каких-либо значений
- var values = [];
ul
  each val in values
    li= val
  else
    li There are no values

for

- for (var x = 0; x < 3; x++)
  li item

while

- var n = 0;
ul
  while n < 4
    li= n++

Миксины (Mixins)

Миксины позволяют создавать повторяющиеся блоки

//- декларация
mixin list
  ul
    li foo
    li bar
    li baz

//- использование
+list
+list

Преобразуются в функции и могут принимать аргументы:

mixin pet(name)
  li.pet= name
ul
  +pet('cat')
  +pet('dog')
  +pet('pig')

Можно устанавливать значения по умолчанию:

mixin elements(num = 10)
   - for (var i = 0; i < num; i++)
      p #{i+1} из #{num}

+elements()  // выведет 10 элементов
+elements(3) // выведет 3 элемента

// Для длинных "значений по умолчанию" проще будет написать условие:
mixin text(sometext)
   - sometext = sometext || 'длинный и очень интересный текст-рыба установленный по умолчанию...'
   p #{sometext}

Миксины могут принимать блоки pug контента, для последующего преобразования:

//- если под миксином указан контент, он будет добавлен в позицию block

mixin article(title)
  .article
    .article-wrapper
      h1= title
      if block
        block
      else
        p No content provided

+article('Hello world')

+article('Hello world')
  p This is my
  p Amazing article

Двойные блоки в миксине можно создавать так 

 // initialization
- blocks = {}

mixin set(key)
	- blocks[key] = this.block

// mixin definition
mixin layout
	block
	.main
		if blocks.main
			- blocks.main()
		else
			//- Do nothing
		
	.side
		if blocks.side
			- blocks.side()
		else
			//- Do nothing
	+set('main')
	+set('side')

p first mixin call
+layout
	+set('main')
		p Main
	+set('side')
		p Side

p second mixin call
+layout
	+set('main')
		p new main
	+set('side')
		p Second side 

p third mixin call
+layout
	+set('side')
		p Third side

Миксины могут принимать неявные аргументы, которые берутся из аргументов переданных в миксин

mixin link(href, name)
  //- attributes == {class: "btn"}
  a(class!=attributes.class href=href)= name

+link('/foo', 'foo')(class="btn")

//- по умолчанию значения в атрибутах экранированы, 
//- что бы избежать повторной экранизации используйте !=

Миксины можно использовать с &attributes

mixin link(href, name)
  a(href=href)&attributes(attributes)= name

+link('/foo', 'foo')(class="btn")

Поскольку Pug пытается определить, является ли содержимое скобок атрибутами или аргументами, лучше использовать запись +link()(class="btn"), во избежании возможных ошибок. Синтаксис +link(class="btn" ) также действителен, но не рекомендован.

Можно указать бесконечное количество аргументов, используя синтаксис “rest arguments”

mixin list(id, ...items)
  ul(id=id)
    each item in items
      li= item

+list('my-list', 1, 2, 3, 4)

Переменная в атрибуте миксина:

- var name = 'Joe'
+user(name)

Миксин в атрибуте миксина:

 +someMixin(['nextMixin'])

Миксины в атрибуте миксина:

// определяем миксины
mixin header(leftside,rightside)

  header
    .head-left-wrap
      for mixleft in leftside
        +#{mixleft}

    .head-right-wrap.top-wrap
      for mixright in rightside
        +#{mixright}

mixin first
  ...

mixin second
  ...

mixin third
  ...

// вызываем
+header(['first', 'second'],['third'])

Вызов миксина в атрибуте блока:

.box(style="background:"+randcolor)

Такой вызов может сработать если в названии миксина нет сложных символов вроде «-» и если миксин не очень сложный. Иногда проще заменить миксин js функцией:

-
   function backimg(url) {
      return "background: url('"+url+"')";
   }  

div(style=backimg("../img/img.jpg"))

Динамические миксины:

+#{mixinName}()

Пример использования динамического миксина:

// создаем миксин который будет генерировать динамические миксины

mixin dynMixin(name)
  +#{name}()

Используется так:

// имеем какие-то миксины
mixin a
  p AAA

mixin b
  p BBB

mixin c
  p CCC


-
  // объявляем переменные
  var choiseMixin;
  var someCondition = 2;

  //
  switch (someCondition) {
    case 1: choiseMixin = 'b'; break;
    case 2: choiseMixin = 'c'; break;
    default: choiseMixin = 'a';
  }

+dynMixin(choiseMixin)
//- равнознчно: +#{choiseMixin}()

Note: Имя динамического миксина должно быть строкой (string).

Простой html текст (Plain Text)

Для вывода html без обработки шаблонизатором есть несколько способов:

Обернуть текст тегом

p Тут находится обычный <em>длинный</em> текст.

 Использовать html занимающий всю строку

<html>

body
  p Indenting the body tag here would make no difference.
  p HTML itself isn't whitespace-sensitive.

</html>

С использованием префикса | (pipe)

p
  | The pipe always goes at the beginning of its own line,
  | not counting indentation.

Для больших html блоков стоит использовать символ . (точка)

script.
  if (usingPug)
    console.log('you are awesome')
  else
    console.log('use pug')
div
  p This text belongs to the paragraph tag.
  br
  .
    This text belongs to the div tag.

Массивы Pug / Jade

Обычный массив:

-
  var count =  [
  'one',
  'two',
  'tree',
  ]

Массив с ключом:

-
  var text_cognitive = [
      {
        name:'Амплификация',
        text: 'вложение в достижение цели больших усилий, чем необходимо, попытка «убить муху кувалдой». Вариант — чрезмерно детальное планирование в условиях отсутствия в достаточном объёме исходных данных и наличия сильно влияющих на результат неопределённых или случайных факторов.'
       },
      {
        name: 'Ускорение',
        text: 'выполнение работы со скоростью большей, чем необходимо или даже допустимо.'
      },
      {
        name: 'Опережение',
        text: 'неоправданно раннее начало действий по достижению цели.'
      },
      {
        name: 'Уклон в сторону поиска информации',
        text: 'тенденция искать информацию даже тогда, когда она не влияет на действия или результат.'
      },
    ]

Выводятся массивы через итерации или с указанием ключей. Миксин с рандомным выводом из массива:

mixin fish(type)
    - var num = Math.floor(Math.random()*txt.length);
    | #{txt[num][type]}

Указание ключа массива в качестве переменной:

// Массив
- var books = [{"id": 1}, {"id": 2},{"id": 3}]

// Первый вариант вывода по id
ul
  for book in books
    li= book.id
  else
    li sorry, no books!


// Второй вариант вывода по id
ul
  - if (books.length)
    -for (var i = 0; i < books.length; i++)
      li= books[i].id
  - else
    li sorry, no books!