БЭМ on Rails



    Здравствуй, <%= habrauser %>!

    Я очень люблю фреймворк Ruby On Rails, он правда очень и очень крут. Он позволяет в кратчайшие сроки реализовать твои замыслы. Раньше я много писал на нем, но сегодня я front-end разработчик. Когда я узнал о методологии БЭМ, я был в полном восторге, потому что так или иначе ты сам приходишь к чему-то подобному. Хорошо, когда дзен-процесс сокращается в разы. О том, что такое БЭМ можно прочитать тут и тут. Недавно прошедший BEMup окончательно расставил все на свои места. Мне были просто необходимы инструменты для работы с БЭМ в рамках проектов на Ruby on Rails. Конкретных решений не существовало, а bem-tools не подходит по вполне понятным причинам, описанным ниже. Я решил написать bem-tools на Ruby.

    Зачем?


    Некогда многие высказывали мысль о том как использовать БЭМ в рельсах, но кроме того как правильно именовать классы в css, я ничего полезного не узнал. Тем более писать такие классы ручками совсем не хочется. Конкретных инструментов предложено не было. А БЭМ это не только css и яваскрипт. Мне хотелось иметь внутри рельс нечто подобное шаблонизатору BEMHTML. Чтобы его использовать можно было бы подключить в рельсы V8 и компилировать шаблоны. Но тогда от рельс мало чего остается и это далеко не Ruby way. Я привык писать шаблоны на HAML и использовать препроцессор SASS. Я очень не хотел, чтобы использование БЭМ в рельсах привносило бы серьезные трудности для понимания и заставляло разработчиков совершать непривычные для них действия. Кроме того в рельсах есть pipeline, который собирает и компилирует ассеты, значит пол работы уже сделано. После долгих размышлений сложились четкие требования к тому что необходимо иметь в виде инструментов:

    1. Одна директория с блоками для всех проектов на Ruby on Rails. В идеале — не только RoR.
    2. Рендеринг блоков и элементов во вьюхах.
    3. Удобное создание/удаление/просмотр блоков/элементов/модификаторов.
    4. Интеграция с pipeline.
    5. Максимальное сходство с bem-tools.


    Почему не bem-tools

    Во-первых, используя в rails приложении bem-tools вы получаете некую штуку сбоку, которая никак вам не подчиняется, вы же не яваскрипт разработчик? Во-вторых, bem-tools тянет за собой еще добрую кучку инструментов через npm, что вам не подвластно. А у рельсового разработчика есть bundler и Gemfile. Зачем мне нужны еще пакетные менеджеры? Используя bundler я всегда могу создавать гемсеты для разных проектов привычным для себя способом. Все зависимости идут вместе с проектом в привычном виде. Ничего дополнительно не нужно. Я устанавливаю гем и у меня все работатет. Никаких дополнительных директорий в корне проекта, кроме самой библиотеки блоков. В-третьих, процесс сборки в рельсах несколько иной, он уже реализован и менять его не хотелось бы. Можно было бы использовать bem-tools только для работы с файловой системой, но это не отменило бы написания самостоятельного кода для рендеринга. Кроме того не получилось бы использовать для паршелов шаблоны. Об описанных инструментах вполне можно думать как о способе организации паршелов в rails приложении.

    Как?


    Создание/Удаление/Просмотр

    Для того чтобы создавать/удалять/просматривать блоки я написал некоторое количество Thor задач. Еще мне показалось, что было бы полезно иметь возможность распределять блоки по группам. Не ручками, а из консоли, и рендерить потом блоки из нужной группы:
    thor bem:create -b filter -G search
    

    И теперь создавать блоки/элементы/модификаторы можно так, причем модификаторы могут быть как со значениями так и без:
    thor bem:create -b test
    
    thor bem:create -b test -e icon
    
    thor bem:create -b test -m large
    
    thor bem:create -b test -m color -v red
    
    thor bem:create -b test -e icon -m file
    
    thor bem:create -b test -e icon -m size -v small
    
    thor bem:create -b test -e icon -m size -v small -T sass
    

    Удаление происходит аналогично:
    thor bem:remove -b test
    
    ...
    

    Просмотр существующих блоков:
    thor bem:list
    
    thor bem:list -G search 
    
    thor bem:list -b test
    
    ...
    

    Для себя я решил немного изменить файловую структуру блока. Я решил вынести элементы блока в директорию /elements, а модификаторы в /mods. Мне показалось это более удобным, чем держать все в одной директории. Вернуться назад можно в любой момент. Сейчас директория с блоками автоматически создается в корне rails приложения и выглядит примерно так:
    Файловая структура
    blocks
    • block_name
      • elements
        • __element_name
          • __element_name.html.haml
          • __element_name.css.sass
          • __element_name.coffee
          • __element_name.md


      • mods
        • _mod_name
          • _mod_name.css.sass
          • _mod_name.coffee
          • _mod_name.md



      • block_name.html.haml
      • block_name.css.sass
      • block_name.coffee
      • block_name.md

    • group_name
      • block_name



    Так же совсем неплохо иметь описание к каждому блоку/элементу/модификатору, причем в привычном markdown. Независимо от технологий, которые использую лично я, вы можете использовать свои, те, к которым давно привыкли. Для этого я создал initializer, в котором определил все настройки BEM. Там вы можете все отредактировать для себя, в том числе префиксы для блоков/элементов/модификаторов.

    Рендеринг

    Благо в рельсах есть хелперы. Благодаря им и хэшам, рендерить блоки в HAML вьюхах можно так:
    = b "test", mods: [{color: "red"}], content: [{ elem: "icon", elemMods: [{size: "small"}] }]
    
    = b "test", mods: [{color: "red"}], content: [{ block: "yeah", content: ["Hello, BEM!"] }]
    
    = b "test", group: "name", mods: [:large, {color: "red"}], content: [] 
    
    = b "test", cls: "custom", attrs: {"data-toggle": "modal"}, content: [] 
    
    = b "test", tag: "article", content: [] 
    

    На мой взгляд смахивает на BEMHTML и это не может не радовать. Для технологий slim, haml, sass, coffee, md я создал шаблоны. После создания блока, шаблон haml(например) станет реализацией блока на технологии haml. По образу и подобию можно создать шаблоны для всех интересующих вас технологий.

    А ассеты?

    С ассетами решается все крайне просто. Можно добавить любую директорию в скоуп, где будут искаться файлы стилей и скриптов. При создании блока/элемента/модификатора в файлы application.css.sass и application.js добавляются строки с определением, например:
    //= require test/elements/__field/__field.coffee
    

    Остальную работу делает Sprockets. Чтобы все работало хорошо, крайне рекомендуется не писать стилей и скриптов в файлах application.css.sass и application.js. Используйте их как конфиги, только для списка того, что ипользуется. Это удобно и без БЭМ. Такой подход позволяет забыть об объявлении используемых блоков/элементов/модификторов впринципе. Все происходит автоматически. Про deps.js можно забыть. Попробуйте!

    Продакшн


    Попробовать использовать данный набор инструментов можно уже сейчас. Я оформил их в виде гема, который вы можете установить себе в проект и использовать БЭМ уже сегодня. Ссылка на гитхаб: bem-on-rails.
    Планы

    1. Флаги bem и js.
    2. Подмешивание блоков и элементов.
    3. Модификаторы с изменением структуры блока. Пока только стиль и яваскрипт.
    4. Bem из командной строки. Запуск тасков через Thor имеет свои недостатки.


    Профит

    Имея директорию с блоками в качестве гит репозитория вы можете подключать свои блоки в любые проекты. Реализация блока/элемента/модификатора может быть как на привычных для рельс технологиях так и на привычных для PHP. Один и тот же репозиторий блоков вы можете использовать как во всех своих проектах на Ruby on Rails (чего бы мне очень хотелось), так и в любых других проектах предполагающих использование БЭМ. Данный набор инструментов призван расширить сферу влияния БЭМ на такой замечательный фреймворк как Ruby on Rails.

    Буду рад любым замечаниям и любой помощи. Спасибо за внимание!

    Stay BEMed!
    Поделиться публикацией

    Комментарии 27

      +6
      Хотелось бы каких-нибудь примеров в статье!
        0
        Примеры добавлю в вики на гитхабе чуть позже.
          0
          Примеров так и нет в вики на гитхабе
        +1
        Одна директория с блоками для всех проектов на Ruby on Rails.

        Уверены?
        Блоки удобнее разделять на библиотеки. Библиотеки — подключать к проектам. На уровне проекта переопределять блоки.
        Плохо представляю, как тут обойтись одной директорией.
        bem-tools не подходит по вполне понятным причинам

        Я не совсем понимаю, по каким.Только из-за того, что инструмент написан не на руби, вы решили писать свою реализацию? Или есть какие-то объективно веские причины?
        Про deps.js можно забыть.

        Забыть про автоматическое разруливание зависимостей и разгребать их руками?
        Чтобы его использовать можно было бы подключить в рельсы V8 и компилировать шаблоны. Но тогда от рельс мало чего остается и это далеко не Ruby way.

        Рельсы — это разве только шаблоны?
          +1
          Спасибо за вопросы. Я бы не хотел чтобы вы думали, что этот гем некоторая конкуренция bem-tools или что-то подобное. Я бы хотел чтобы было совсем наоборот.

          Давайте по порядку.
          Уверены?

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

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

          Вы видимо пропустили описание процесса сборки ассетов. Все собирается автоматически. Я хотел сказать, что вам не нужно для бандлов прописывать зависимости от блоков/элементов/модификаторов.
          Рельсы — это разве только шаблоны?

          В том то и дело, что нет. Рельсы это целая культура.
            0
            Доопределение блоков — не только css, но и логики, и шаблонов — мне кажется, нужно обязательно.
            Про ассеты не пропустил, в части про ассеты нет ничего про разруливание зависимостей между блоками. Или это я что-то не так понял?
              0
              Я видимо тоже не совсем понимаю, что вы имеете ввиду про ассеты. Можно поподробнее вопрос? Согласен, что доопределение блоков очень нужная функциональность, но в текущей реализации ее нет, возможно появится позже.
                +1
                Я имею в виду, что, используя bem-tools и deps.js,. я могу указать, что блок А зависит от блока Б, а блок Б от блока С. Потом подключить к странице блок А — и получить собранные в правильном порядке скрипты и стили всех трех блоков.
                  0
                  Я понял. Вообще я не поддерживаю политику зависимости блоков друг друга. Сейчас вы это не сможете реализовать, если только не пропишите в явном виде классы в шаблоне. С яваскриптом сложнее. Учту на будущее. Спасибо за комментарий. Порядок сборки блоков можно регулировать пока только в файлах application.css, application.js, где ведется список всех компилируемых блоков.
                    0
                    Вообще я не поддерживаю политику зависимости блоков друг друга.

                    Блоки зависят друг от друга, в любом случае. Простейший пример — блок формы зависит от блоков инпутов.
                    Кроме того, блоки зависят от своих элементов. Бывает, что один блок с разными модификаторами зависит от разного набора элементов.
                    Порядок сборки блоков можно регулировать пока только в файлах application.css, application.js, где ведется список всех компилируемых блоков.

                    Опасный подход. Ручное разруливание зависимостей ведет ошибкам. Хотя бы что-нибудь вроде require.js используйте, что ли. Но, видите — require он все-таки про js. а deps.js — про любые технологии.
                      0
                      Продукт крайне сырой и желать от него полной идентичности bem-tools и функциональности рано. Спасибо за наставления. Я постараюсь все это учесть в будущем.
          +3
          Привет!
          А ты читал про второй митап по БЭМ на YaC 2013 в Москве, который будет 2 октября? Мы хотим собрать наше коммьюнити и обсудить разные темы. БЭМ + Ruby вполне могло бы быть одной из них.

          ru.bem.info/blog/2013/09/bemup-yac2013/
            +1
            Привет! Читал конечно! На YaC зарегистрировался. Хотел подать заявку, чтобы выступить с темой, но не успел до 10го сентября.
              0
              Подавай к нам на митап =)
                0
                если решиь, напиши до среды максимум. хотим в чт программу объявить.
                  0
                  Понял, ок. Напишу завра, во вторник, ок?:)
                    0
                    ага!
                    круто, что слоган наш прижился =) прям душу греет!
            +2
            «Группа блоков» в БЭМ методологии называется «уровень переопределения». В bem-tools это параметр -l
              0
              Известно ли автору какое-нибудь не такое ядерное и, фактически, заменяющее html-нотацию, решение, помогающее генерировать css-классы у элементов в соответствии с БЭМ?

              Было бы здорово, если бы можно было б написать, например:

              <%= block name: 'user' do %>
                <div>
                  <p class="_name">
                    <%= user.name %>
                  </p>
                  <p class="_age">
                    <%= user.age %>
                  </p>
                </div>
              <% end %>
              


              и получить на выходе:

              <div class=”b-user”>
                <p class="b-user_name">
                   <%= user.name %>
                </p>
                <p class="b-user_age">
                  <%= user.age %>
                </p>
              </div></code>
              


                0
                Такого решения нет. К сожалению или нет не знаю. А чем сложнее писать <%= e «element» %>?
                  0
                  Не совсем Вас понял. Как бы выглядел мой код с <%= e «element» %>?
                    0
                    Видимо я тоже не все понял. Выглядело бы вот так:
                    = b "test", mods: [{color: "red"}], content: [{ elem: "icon", elemMods: [{size: "small"}] }]
                    


                    Оформи хотелку в issues, пожалуйста, я поразмышляю. Гем так или иначе ждут большие перемены и интеграция с bem-tools, возможно туда получится добавить твою просьбу.
                      0
                      Давайте сперва разберёмся.

                      Меня расстраивает, что приведённый код уже далеко не html и даже не haml. Быть может, я старомоден, но я не вижу ничего плохого в html. Я очень надеюсь, что автор не предлагает все элементы на странице генерировать с помощью подобных конструкций. Мне кажется, это довольно непросто читать.

                      Сравните:

                      <div class="user">
                        <p class="user_name">
                          <%= user.name %>
                        </p>
                        <p class="user_age">
                          <%= user.age %>
                        </p>
                      </div>
                      


                      c

                      <%= b "test", content: [
                        { elem: "name", content: user.name },
                        { elem: "age", content: user.age }
                      ] %>
                      
                      blocks/user/user.html.erb
                      <div class="user">
                        <%= content %>
                      </div>
                      
                      blocks/user/elements/name.html.erb
                      
                      <p class="user_name">
                        <%= content %>
                      </p>
                      
                      blocks/user/elements/name.html.erb
                      
                      <p class="user_age">
                        <%= age %>
                      </p>
                      


                      Лично мне далеко не всегда нужно каждый элемент страницы делать реюзабельным из других проектов, однако хочется для всех принципиально разных элементов делать уникальные классы, чтобы, собственно иметь возможность обращаться к ним из файла стилей без использования сложных многоуровневых селекторов, собственно, что и предлагает делать БЭМ.
                        0
                        Не нужно — не делай. Мысль какрас в том чтобы писать таким образом те блоки, которые ходят из проекта в проект. Это есть тот самый haml, b — хелпер всего лишь. Ты можешь писать как привык на haml и вставлять в общий код переносимые блоки, это не новый шаблонизатор ни в коем случае, не думай так. В итоге тебе нужен хелпер генерации классов, тк писать ручками лень, ок. Пиши в хотелки.
                          0
                          Ну отлично. Этого я и хотел, в общем-то, услышать.
                  0
                  Еще в пользу отсутсвия такого, что гем имеет настраиваемый синтаксис БЭМ нотации. Не все пишут именно так, то есть не все так разделяют элементы и модицикаторы. В Европе принят несколько другой синтаксис. Хорошо бы такие пожелания направлять сразу в issues на GitHub.
                    0
                    Ну, синтаксис разделения блока-элемента-модификатора действительно не принципиален, подошёл бы любой.

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

                Самое читаемое