Используем Haml для генерации HTML

Недавно наша небольшая команда полностью перешла на верстку с использованием препроцессора Haml. Прежде, чем это произошло, было составлено несколько справочных документов, по которым ребята включались в работу с новым инструментом. Так как эти материалы мне приходилось готовить самостоятельно, я решил потратить еще немного времени и переработать их в небольшую заметку на тему основных преимуществ, которые дает Haml над олдскульной версткой и вынести на рассмотрение некоторое количество примеров для быстрого старта.

Если вы не понаслышке знаете о Less или Sass, то для понимая преимуществ, которые может дать использование Haml вам не потребуется много времени. Haml представляет из себя язык разметки с упрощенным синтаксисом. Написанный на нем код может быть компилирован в хорошо знакомый HTML. Как и другие препроцессоры, он предлагает не только видоизмененный синтаксис, но и новые конструкции языка: условные операторы, циклы, вставки кода из других файлов и т.п.




То, как установить Haml под Windows я описывал в предыдущей заметке. Там же есть инструкция по конфигурации PhpStorm. А сейчас предлагаю начать с небольшого куска кода:

#wrapper
  %section#content.main-content
    %header.header
      %h1 Заголовок h1
      .post-date 28.08.2014
    .post-entry
      %p Субъективное восприятие косвенно
      %p Эстетическое воздействие притягивает литературный стиль.

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

Имена тэгов начинаются с символа %, далее следует имя тэга и его параметры class/id, если они необходимы. Если имя тэга опущено, как в случае с блоком #wrapper, то будет использоваться div. Как вы видите, многое направлено на сокращение количества кода и это не может не радовать.

После компиляции получим следующий html:

<div id="wrapper">
  <section class="main-content" id="content">
    <header class="header">
      <h1>Заголовок h1</h1>
      <div class="post-date">28.08.2014</div>
    </header>
    <div class="post-entry">
      <p>Субъективное восприятие косвенно</p>
      <p>Эстетическое воздействие притягивает литературный стиль.</p>
    </div>
  </section>
</div>

Теперь давайте рассмотрим несколько более интересных и полезных примеров.

Переменные в Haml

С переменными все просто. Объявление переменной и вывод на печать демонстрирует следующий пример:

- some_var  = 'Значение переменной'
%p
  = some_var

На выходе получаем:

<p>
  Значение переменной
</p>

Вставка кода из других файлов

Это то, чего мне катастрофически не хватало в обычном html. Если вы верстаете несколько макетов, скорее всего, они будут содержать одинаковые блоки: меню, виджеты, постраничная навигация и т..п. Каждый такой блок приходилось копипастить из файла в файл. И если макетов, содержащих меню будет штук 10-15, у вас непременно начнутся сложности, когда потребуется внести правки в этот блок. Используя Haml, вы можете вынести код меню в отдельный файл и делать инклуды там, где это необходимо.

%header.page-header
  .container
    = Haml::Engine.new(File.read('./includes/menu-main.haml')).render
    .login

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

= Haml::Engine.new(File.read('./includes/head.haml')).render(parent, :title => "Main page")

Обратите внимание на вывод значения переменной title в подключаемом файле

!!!5
%html
  %head
    %meta{:charset => 'utf-8'}
    %title= title
    %link{:href => './assets/css/styles.css', :type => 'text/css', :rel => 'stylesheet'}

Условный оператор If

Принимая по внимание наличие переменных в Haml, можно лишь порадоваться тем возможностям, которые открывает перед нами условный оператор if. Например, опираясь на значение переменной, переданной в подключаемый файл, мы можем вернуть тот кусок кода, который нужен именно для этого инклуда. Чуть позже я опишу эту ситуацию подробнее, а пока просто ознакомьтесь с синтаксисом:

- myVar  = 9

- if myVar == 10
  %p Значение переменной равно 10
- elsif myVar == 9
  %p Значение переменной равно 9
- else
  %span Я не знаю, чему равна переменная...

Обратите внимание на elsif — это не опечатка.

Циклы в Haml

Циклы — одна из моих любимых и часто используемых фич. Как минимум, они избавляют от некоторых рутинных действий. Предположим, необходимо сверстать блок постраничной навигации. С использованием цикла и упомянутого выше if это будет выглядеть так:

.nav.nav-pagination
  %ol.pages
    - (1..6).each do |i|
      %li.page
        - if i == 3
          %span.current #{i}
        - else
          %a{:href => '#'} #{i}

Компилируем и получаем:

<div class="nav nav-pagination">
  <ol class="pages">
    <li class="page">
      <a href="#">1</a>
    </li>
    <li class="page">
      <a href="#">2</a>
    </li>
    <li class="page">
      <span class="current">3</span>
    </li>
    <li class="page">
      <a href="#">4</a>
    </li>
    <li class="page">
      <a href="#">5</a>
    </li>
    <li class="page">
      <a href="#">6</a>
    </li>
  </ol>
</div>

Если вдруг потребуется увеличить или уменьшить количество страниц, сменить имена классов или добавить дополнительную разметку, достаточно будет нескольких мелкий правок.

Массивы

Если есть циклы, то массивы должны быть непременно. Объявление массива и присвоение значения аналогично переменным:

-# Массив
- @array_1 = ['Item1', 'Item2', 'Item3'];

-# Ассоциативный массив
- @array_2 = {'key1' => 'Item1', 'key2' => 'Item2', 'key3' => 'Item3'};

Обход в цикле простого массива показан выше, работа с ассоциативным массивом не сильно отличается:

%ul
  -@array_2.each do |key, value|
    %li #{key}:#{value}

Получим список:

<ul>
  <li>key1:Item1</li>
  <li>key2:Item2</li>
  <li>key3:Item3</li>
</ul>

Что еще?

Помимо описанных выше конструкций, Haml поддерживает фильтры и хелперы, экранирование символов, комментарии и некоторые другие возможности. Все они неплохо описаны в официальной документации. Впрочем, стоить отметить, что местами она слишком уже лаконична.

Живой пример

Для наглядности я хочу продемонстрировать использование всех описанных выше возможностей Haml на «живом» примере, который должен помочь осознать ситуации, где Haml дает существенные преимущества над привычной html версткой.

Предположим, что у нас есть типовая задача по верстке трех страниц: главная, о компании и контакты. Все страницы имеют сквозные блоки: шапка и футер. Между ними уникальная центральная часть.

Для сквозных блоков и центральной части страницы создадим три файла: index.haml, header.haml и footer.haml. Еще один, назовем его head.haml, — для секции head, так как она тоже является одинаковой для всех страниц.

index.haml

!!!5
%html
  = Haml::Engine.new(File.read('./includes/head.haml')).render(parent, :title => "Главная страница")
  %body
    = Haml::Engine.new(File.read('./includes/header.haml')).render(parent, :menu_current => "index")
    .page-content
      %p Контентная часть
    = Haml::Engine.new(File.read('./includes/footer.haml')).render

Файл index.haml ничего особенного из себя не представляет. Мы просто включаем в него три общих файла, передавая в них значения для нескольких переменных.

head.html

%head
  %meta{:charset => 'utf-8'}
  %title= title
  - @scripts = ['jquery.min.js', 'custom.js'];
  - @scripts.each do |script|
    %script{:src => "./assets/js/#{script}"}

Значение title мы получим из переменной, а массив делает удобным подключение скриптов, если их много.

header.haml

%header.page-header
  %p Header страницы
  - @menu_items = {'index' => 'Главная', 'about' => 'О компании', 'contacts' => 'Контакты'};
  %nav.menu.menu-main
    %ul
      -@menu_items.each do |key, value|
        -if menu_current == key
          %li.menu-item.current-menu-item
            %span #{value}
        -else
          %li.menu-item
            %a{:href => "#{key}.html"} #{value}

Здесь мы формируем шапку и главное меню, которое строится на основе элементов ассоциативного массива. Мы проверяем значение переданной извне переменной menu_current на равенство ключу текущего элемента массива. Если они равны, значит необходимо отметить текущий пункт меню как активный. Переменная menu_current дает нам возможность определить то, из какого файла был подключен файл header.haml.

footer.haml

%footer.page-footer
  .copyright © 2014

Можно было обойтись без footer.haml, но чтобы не было чувства недосказанности, упомяну и его.

После компилирования index.haml мы получим желаемый результат:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Главная страница</title>
    <script src="./assets/js/jquery.min.js"></script>
    <script src="./assets/js/custom.js"></script>
  </head>
  <body>
    <header class="page-header">
      <p>Header страницы</p>
      <nav class="menu menu-main">
        <ul>
          <li class="menu-item current-menu-item">
            <span>Главная</span>
          </li>
          <li class="menu-item">
            <a href="about.html">О компании</a>
          </li>
          <li class="menu-item">
            <a href="contacts.html">Контакты</a>
          </li>
        </ul>
      </nav>
    </header>
    <div class="page-content">
      <p>Контентная часть</p>
    </div>
    <footer class="page-footer">
      <div class="copyright">© 2014</div>
    </footer>
  </body>
</html>

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

  • более лаконичный синтаксис;
  • отсутствие закрывающих тэгов (меньше вероятность ошибки);
  • дополнительные возможности (циклы, условия, хелперы и т.п.);
  • возможность повторно использовать код;
  • повторно используемый код проще поддерживать.
  • Попробуйте и вам понравится :)

Альтернативы Haml

Если Haml вам не нравится, хотя сама идея использования препроцессора для html приходится по душе, посмотрите в сторону Jade. Принципиальные отличия я вижу только в синтаксисе и в этом плане Jade мне нравится существенно меньше. Без подсветки код становится совершенно не читаемым, хотя в остальном отличная альтернатива.

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

  1. DarthVader

    Синтаксис непривычный, конечно, но кажется простым. А как обстоят дела с подсветкой синтаксиса и код фолдингом?

    • Ни с подсветкой, ни с код фолдингом проблем нет. И то, и другое присутствует во всех популярных редакторах, как в платных, так и в бесплатных.

  2. А чем он лучше или быстрее написания html на emmet?

    • Между HAML и Emmet нет ничего общего, поэтому не представляю, как их можно сравнивать. Где у Emmet циклы, инклуды файлов, условные операторы? Emmet, можно сказать, это просто синтаксический сахар.

  3. Ярослав

    Вы уверены, что синтаксис передачи значения переменной для title в примере описанном выше верный? Потому-что у меня компилится захардкоженный:
    = title
    что я делаю не так?

  4. Ярослав

    пожалуйста:
    http://prntscr.com/64lnyr

  5. Ярослав

    Это — да, пробел есть, я в курсе, вот только если его убрать, компилятор выдаёт ошибку:
    http://prntscr.com/64lx9a

    может я Вам на почту напишу, чтобы не засирать Ваши комментарии?

    • Мне комментарии не жалко :)

      Что касается ошибки, то ничего удивительного. Переменная title локально не объявлена и компиляция не проходит. Ошибку можно либо-игнорировать, либо в настройках вотчера добавить директорию в исключения.

      У использования подключаемых файлов есть единственный минус — нужно исключать подключаемые файлы из компиляции по сохранению, а компиляцию осуществлять через сохранение «родительского» файла.

  6. Ярослав

    Олег, я пытался добавить директорию с партиалами в исключение в вотчере
    files: [‘static/haml/*.haml’, ‘!static/haml/_*.haml’]
    но не помогло
    компилятор всё-равно как-то умудряется компилить инклуды, возможно, нужно лучшее решение для исключения

    • У вас определенно что-то не то с настройками. Может не выбрали созданный скоуп и вотчер продолжает работать c «Project Files»? У меня все отлично работает и лишние файлы не компилятся. Может скриншот поможет

  7. Ярослав

    Всё, разобрался, глупая ошибка, возможно кодировка, закрался какой-то символ, заново всё переписал и всё заработало… бывает

  8. SlowDream

    А как компилятор реагирует на обычный html ? К примеру я бы хотел использовать только часть функций из хамл.

  9. Сергей

    Олег, не сталкивался с ошибкой?
    «in `block in render’: can’t modify frozen NilClass (RuntimeError)»
    Все делаю как по примеру, но вот с этим куском никак не хочет компилиться
    .render(parent, :title => «Главная страница»)
    если просто .render, то все норм
    Может я где туплю…

    Спасибо большое за статью. осталось только разобраться с передачей переменных в подключаемый файл и будет красота.

    • Сергей

      Хм, не знаю как, заработало

      !!!5
      %html
      = Haml::Engine.new(File.read(‘./includes/head.haml’)).render(Object.new, :title => «Hello, world!»)

  10. Кирилл

    Добрый вечер, спасибо за статьи. Вопрос возник по включению файлов, есть ли возможность передать в качестве параметра массив. Было бы полезно при верстке меню, включая подготовленный шаблон и передавая туда пункты меню?

  11. Олег, подскажите пожалуйста.
    Я сверстал отдельно header и вынес его в инклуды. Когда подключаю header на страницы все нормально, но на каждой странице в меню должен быть свой class=»active». Можно сделать так, чтобы при компиляции html на нужную лишку в меню цеплялся class=»active»?

    • Юрий, то, о чем вы спрашиваете, есть в примерах этой заметки :) Могу вам лишь посоветовать перечитать её еще раз и все получится, уверен.

  12. Как добавить с помощью haml текст символ » / «. Этот символ он понимает как спец, но как вставить как текст ?

  13. Dek4nice

    «Принципиальные отличия я вижу только в синтаксисе и в этом плане Jade мне нравится существенно меньше»
    Вы серьезно? Нагромождение синтаксиса имеет положительные стороны?
    = Haml::Engine.new(File.read(‘./includes/menu-main.haml’)).render против include includes/menu-main.jade у jade.

    Необходимость в собаке перед тегом, в том время как у jade любые теги.
    Их принципиальное различие в том что у jade есть миксины.

    • Абсолютно серьезно.
      Во-первых, у HAML есть Ruby и через :ruby можете себе любую примесь написать. Инклуды смотрятся упорото, согласен, но через тот же :ruby можно завернуть во что-нибудь красивое и лаконичное.

      А что касается синтаксиса, то мне проще, когда тэги и строки имеют визуальное отличие. Без подсветки синтаксиса, если друг пришлось оказаться в такой ситуации, Jade шаблоны, лично для меня, вообще нечитаемы. Впрочем, это уже вкусовщина.

  14. Алексей

    Все ровно не понимаю зачем использовать препроцессоры для html, ведь можно использовать php, он даёт намного больше функционала. Не могли бы вы разъяснить?

    • C PHP вы будете писать чистый HTML + PHP. У HTML не самый лаконичный синтаксис. Haml в этом плане куда компактнее. Для меня это одно из основных преимуществ. Циклы, условия и прочее используется не так часто, чтобы в виде замены брать полноценный ЯП.

  15. DedOstapdatt

    Сидим со старухой и думу думаем: что нам простым смертным и небогатым людям ставить на стол из еды и каким спиртным угостить себя или случайных гостей, родственников.

    Пока одна идея: картошку с курицей нажарим, салатик зимний сварганим, да спиртика прикупим.
    А как у вас с этим?
    Поздравляю всех с наступающим Новым 2023 годом!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *