Заметки за 2014 год (страница 2)

Автоматизируем запуск QUnit тестов

QUnit очень старый фреймворк для тестирования браузерного JavaScript. Чтобы запустить тесты, нужно создать небольшой HTML-файл с несколькими div-ами, стилями и скриптами QUnit и, собственно, тестовыми скриптами. Затем этот файл можно открывать в разных браузерах и смотреть отчёт о выполнении тестов.

Запуск этих тестов можно поручить PhantomJS. Для этого нужно воспользоваться скриптом QUnit PhantomJS Runner.

$ phantomjs runner.js test/index.html

Если у вас используется Grunt для запуска различных задач, то для него есть плагин grunt-contrib-qunit. Он так же использует PhantomJS для прогона тестов.

grunt.initConfig({
  qunit: {
    all: ['test/index.html']
  }
});
Оставте свой комментарий

Декомпозиция блоков в стиле БЭМ

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


/* Плохой пример! Не повторяйте такого */
.block {
    &__element {
    }
    &__child {
        color: black;
    }
    &__element_active &__child {
        color: red;
    }
}

Если приглядеться внимательно к получившейся конструкции, то можно найти аналогию с модификатором блока. Так оно и есть. Опишем элемент как новый блок с именем block-element и всё встанет на свои места.


/* Хороший пример. Декомпозиция упростила
   взаимоотношения между элементами */
.block {
    &__element {
    }
}
.block-element {
    &__child {
        color: black;
    }
    &_active &__child {
        color: red;
    }
}

На одних и тех же «физических» DOM-элементах мы разместили два «логических» блока.


<div class="block">
    <div class="block__element block-element">
        <span class="block-element__child">Some text</span>
    </div>
</div>

Миксин для генерации Блока


<article class="block">
    <h1 class="block__title">
        <span class="block__title-wrap">My cool title</span>
    </h1>
    …
</article>

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


.cool-title(@color: yellow) {
    font-size: normal 24px/1 sans-serif;
    &-wrap {
        background: @color;
    }
}

.block {
    &__title {
        .cool-title();
    }
}

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

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


.cool-title(@color: yellow) {
    font-size: normal 24px/1 sans-serif;
    &__wrap {
        background: @color;
    }
}

.block-title {
    .cool-title();
}

Опять у нас на одних и тех же DOM-элементах прекрасно уживаются два блока.


<article class="block">
    <h1 class="block__title block-title">
        <span class="block-title__wrap">My cool title</span>
    </h1>
    …
</article>

Комментарии к заметке: 2

Генерация названий селекторов в стиле БЭМ с помощью препроцессоров

Методология БЭМ никогда не подразумевала того, что длинные названия классов в селекторах типа .block__element_modifier будут записываться вручную. В Яндексе для этого сразу используются специальные утилиты, которые генерируют стили и разметку блока. Если вы используете препроцессоры для генерации CSS, то вам тоже не нужно каждый раз повторять названия блока или элементов. Сейчас я расскажу, как это делается.

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


.header {
    .title {
        font: bold 24px/1 sans-serif;
    }
}

После трансформации получаем CSS:


.header .title {
    font: bold 24px/1 sans-serif;
}

Вместо «родительской части» селектора можно использовать символ & . Удобно, когда нужно применить свойства и выбранному элементу, и его потомкам или псевдо-классам.


.header {
    a {
        &, &:hover, &:focus {
            color: white;
            text-decoration: none;
        }
    }
}

CSS:

.header a,
.header a:hover,
.header a:focus {
    color: white;
    text-decoration: none;
}

LESS (а с недавнего времени и SASS) подставляют в новый селектор вместо & часть родительского селектора без каких-либо изменений. Так же нет ограничений на последующие или предыдущие символы в новом селекторе. Поэтому вместо каскада можно генерировать простые селекторы.

.header {
    &__link {
        &, &:hover, &:focus {
            color: white;
            text-decoration: none;
        }
    }
}

CSS:

.header__link,
.header__link:hover,
.header__link:focus {
    color: white;
    text-decoration: none;
}

Аналогично формируем простые селекторы для модификаторов элементов.


.header {
    background: white;
    &__title {
        font: bold 24px/1 sans-serif;
        &_featured {
            font-size: 30px;
        }
    }
}

CSS:

.header {
    background: white;
}
.header__title {
    font: bold 24px/1 sans-serif;
}
.header__title_featured {
    font-size: 30px;
}

Вложенные селекторы превратились в простые.

Важно знать

А теперь несколько простых правил, чтобы обладая этим знаниями вы «не прострелили себе ногу».

Название блока используется только один раз

Оно объявляется на первом уровне и нигде больше не повторяется. Исключения могу составлять только сложные случаи модификаторов. Однако, если вам это понадобилось, то скорее всего, стоит пересмотреть структуру элементов и модификаторов. Уверен, что найдётся способ упросить блок или декомпозировать его.

.block {
    // стили блока
}

Элементы идут на втором уровне вложенности


.block {
    // стили блока
    &__element {
        // стили элемента
    }
    &__title {
    }
}

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

.block {
    &__element {
        &-wrapper {
            // Пример плохого элемента.
            // Его будет трудно отыскать.
        }
    }
}

Никогда не делайте подобного. Такой селектор очень трудно будет найти в коде. В примере название элемента element-wrapper разорвано на две части. Пишите название элементов полностью даже если они частично повторяют уже существующие.

Псевдо-классы, псевдо-элементы и модификаторы элементов допускается писать на третьем уровне вложенности


.block {
    &__element {
        &_modifier {
            // стили модификатора элемента
        }
        &_modifier_value {
            // модификатор со значением не разбиваем на части
        }
        &:hover {
            // псевдо-класс — это тоже модификатор
        }
    }
}

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

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


.block {
    &__element_active {
        // модификатор элемента на втором уровне
    }
    &__element_modifier_good {
        // пример модификатора со значением
    }
}

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

Модификаторы блока могут участвовать в каскаде и располагаются на втором уровне

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


.block {
    background: white;
    &__title {
        color: black;
        font: bold 24px/1 sans-serif;
    }
    &_featured {
        background: black;
    }
    &_featured &__title {
        color: white;
        font-size: 30px;
    }
}

Изящность кода чуть теряется из-за того, что нам важно сохранить значение & . Как только мы перейдём на следующий уровень вложенности, то использовать его в названии элемента уже не сможем — нужно будет писать полное название селектора вместе с названием блока, чего мы всячески избегаем.


.block {
    background: white;
}
.block__title {
    color: black;
    font: bold 24px/1 sans-serif;
}
.block_featured {
    background: black;
}
.block_featured .block__title {
    color: white;
    font-size: 30px;
}

Заключение

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

Эта статья была любезно переведена на английский язык Варей Степановой и опубликована во «Frontend Babel» — Generating BEM selectors with CSS preprocessors.

Комментарии к заметке: 23

Динамическая загрузка «отзывчивых» ресурсов

Любой медиа-запрос можно выполнить в реальном времени с помощью JavaScript. Создать экземпляр объекта, который возвращал бы результат проверки Media Query или оповещал бы об изменении этого результата, можно при помощи метода .matchMedia(). Все современные браузеры уже реализовали этот метод в объекте window. Если же вам нужна поддержка старых браузеров, то можно воспользоваться полифилом matchMedia.js.


var mql = window.matchMedia("(min-width: 720px)");
if (mql.matches) {
  // десктоп или планшет
} else {
  // мобильник
}

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


var Environment = (function () {
  var _mqDesktop;

  function getDesktopMediaQuery() {
    return _mqDesktop ||
      (_mqDesktop = window.matchMedia("(min-width: 720px)"));
  }

  function isDesktop() {
    return !!(window.matchMedia && getDesktopMediaQuery().matches);
  }

  function once(mq, callback) {
    function handler() {
      mq.removeListener(handler);
      callback();
    }
    mq.addListener(handler);
  }

  return {
    /**
     * Test the environment against “desktop” media query
     */
    isDesktop: isDesktop,
    /**
     * Invoke callback when the environment becomes like “desktop”.
     */
    waitForDesktop: function (callback) {
      if (isDesktop()) {
        callback();
      } else {
        once(getDesktopMediaQuery(), callback);
      }
    }
  };

})();

Далее в коде приложения сделать вызов:


Environment.waitForDesktop(function () {
  Modernizr.load([
    "/styles/desktop.css",
    "/scripts/desktop.js"
  ]);
});

Таким образом, файл стилей /styles/desktop.css и скрипт /scripts/desktop.js загрузятся только когда ширина экрана будет больше 720 пикселей.

Из-за того, что файлы загружаются асинхронно появляется побочный эффект — нет никакой гарантии, что выполнение скрипта начнётся только после применения стилей. Например, габариты блока будут не верны или блок вообще может быть невидимым во время инициализации компоненты. Это нужно учесть и дождаться полной загрузки CSS файла с перерисовкой страницы.

Проверить, что файл стилей гарантировано применился можно с помощью специальных стилей-маркеров.

mobile.css:

.style-detector {
  z-index: 1;
}

desktop.css:


@media (min-width: 720px) {
  .style-detector {
    z-index: 2;
  }
}

А колбек обернуть в функцию, которая ожидает нужного значения свойства z-index .


function waitUntil(value, cb) {
  var body, detector = document.querySelector(".style-detector");
  if (!detector) {
    detector = document.createElement("div");
    detector.className = "style-detector";
    body = document.getElementsByTagName("body")[0];
    body.insertBefore(detector, body.firstChild);
  }

  function getStyle(el, cssProperty) {
    return (getComputedStyle !== "undefined") ?
      getComputedStyle(el)[cssProperty] : el.currentStyle[cssProperty];
  }

  var i = 100; // timeout = 25 sec
  (function loop() {
    var v = getStyle(detector, "zIndex");
    if (v != value) {
      i = i - 1;
      if (i >= 0) {
        setTimeout(loop, 250);
      } else {
        cb();
      }
    } else {
      cb();
    }
  }());
}

Полный код модуля доступен на Гитхабе.

Комментарии к заметке: 5

Поиск объединённых веток в Git

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

Если вы придерживаетесь подхода «одна фича — один бранч» и не делаете значимых комитов без веток, то вам подойдёт другой способ:


git branch -r --merged staging | \
  grep -v -E `(git branch -r --merged master | \
    tr "\n" " " | \
    sed -e "s/^ *//" -e "s/ *$//" | \
    tr -s " " "|")`

Эта последовательность команд выдаст список веток, которые были объединены в staging, но ещё не попали в master.

Оставте свой комментарий