...

среда, 9 июля 2014 г.

Приводим в порядок css-код. Опыт Яндекса

Всем привет!

Я работаю над фронтендом огромного проекта – поисковой выдачи Яндекса. И у нас, как и у любого другого большого веб-проекта, есть огромное количество css-кода и немаленькая команда, которая с ним взаимодействует.


Когда много людей используя разные инструменты, пишет и редактирует css, со временем этот css может получиться очень запутанным, неконсистентым и в целом начинает выглядеть плохо. Например, кому-то удобнее писать вендорные префиксы в одном порядке, кому-то – в другом, кто-то ставит кавычки вокруг url, кто-то – нет, а кто-нибудь фикся срочную багу к релизу мог бы, к примеру, написать position: relative в начале блока свойств, незаметив что где-нибудь внизу между color и box-shadow, уже есть position: absolute, и долго гадать, почему у него ничего не работает.



Но несмотря на то, что все пишут код по-разному, у нас в репозитории идеальный порядок: css-код полностью консистентен, и прекрасно выглядит. Весь.


Как мы этого добились, можно прочитать под катом.



Первые шаги




Если мы хотим, чтобы весь css писался по кодстайлу, то первая очевидная мысль – это чтобы какой-нибудь самый-главный-начальник сказал: «Надо писать так-то и так-то, соблюдайте», а несоответствия отлавливать на код ревью. Звучит круто, но, к сожалению, только звучит, а на практике получается так себе:


  1. Все мы люди — а человеку немного сложно запомнить точный-рекомендованный-порядок всей кучи css свойств, и никогда в нем не ошибаться.

  2. Код ревью в таком случае превращается в набор правок по кодстайлу, а хотелось бы чтобы в нем обсуждали логику и архитектуру.

  3. Все ошибки в этом месте нужно исправлять руками, а все несоответствия уточнять в какой-нибудь доке.




В общем, выглядит как-то не очень, отнимает кучу времени и, что самое главное – очень расстраивает разработчиков.

Чтобы разработчики ходили довольные, пришлось искать какое то другое решение, и оно было найдено.

Немного о роботах




Был один старый робот — CSScomb, который решал самую важную часть нашей задачи:

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


  1. Кроме этого он не умел ничего, а ведь очень хотелось и кавычки где не надо убирать, и вендорные префиксы лесенкой выстраивать...

  2. Был написан на PHP и регэкспах, а также не предусматривал расширения.




И хотя, конечно, PHP в среде js-программистов немного известнее чем c++, это все-таки не совсем то, чего бы хотелось.

mishanga из нашей команды, посмотрел на все это дело и решил сделать инструмент, который был бы лишен этих недостатков: умел много разных штук, был бы написан на js, и мог легко расширяться. И хотя сотрудники Яндекса принимают активное участие в разработке, CSScomb.js – полноценный опенсорсный проект, давно вышедший за рамки Яндекса.


Знакомьтесь, CSScomb.js — это новая версия старого CSScomb, умеющая кучу всяких крутых вещей.


Как это выглядит ?




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

.block {
-webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
-moz-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
z-index: 2;
position: absolute;
height: 2px;
width: 2px;
color: #FFFFFF;
}




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

% git commit block.css -m"Add My awesome css"
Code style errors found.
! block.css




Коммитнуть такой css-файл не удастся. Это происходит потому что в нашем репозитории в гите стоит прекоммит хук, проверяющий с помощью CSScomb все изменения в css-файлах, которые мы хотим закоммитить. Работает он в режиме линтера. Хук при этом довольно умный, поэтому проверяет только то, что мы собираемся коммитнуть, а не все подряд.

Увидев такое сообщение, мы не расстраиваемся, а просто просим CSScomb.js все поправить:



% csscomb block.css




Вот, теперь другое дело:

.block
{
position: absolute;
z-index: 2;

width: 2px;
height: 2px;

color: #fff;
-webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
-moz-box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
box-shadow: 0 0 0 1px rgba(0, 0, 0, .1);
}




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

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


А внутре у ней неонка




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

В основе CSScomb.js две вещи: система плагинов и css-парсер gonzales-pe — очень крутая штука, которая умеет работать не только с чистым css, но и препроцессорами вроде Sass или LESS.


Начнем с парсера. Он натравливается на css-код и строит на его основе AST. Например, для:



.block
{
position: absolute
}




AST будет выглядеть так:

[ "stylesheet",
[ "ruleset",
[ "selector",
[ "simpleselector",
[ "class", [ "ident", "block" ] ],
[ "s", "\n" ]
]
],
[ "block",
[ "s", "\n " ],
[ "declaration",
[ "property", [ "ident", "position" ] ],
[ "propertyDelim" ],
[ "value", [ "s", " " ], [ "ident", "absolute" ] ]
],
[ "s", "\n" ]
]
],
[ "s", "\n" ]
]




Это формат очень громоздкий и неудобный для чтения человеком, зато очень удобен для автоматической обработки, которой и занимаются плагины. Всеми дальнейшими преобразованиями css занимаются именно они, причем работают плагины не непосредственно с css-кодом, а с AST, которое намного проще обрабатывать. Какие плагины использовать, а также их настройки указываются в отдельном файле с конфигурацией.

В примере выше мы выше мы не поставили точку с запятой после absolute. Плагин, который отвечает за детектирование и исправление точек с запятыми, это заметил и исправил через модификацию AST(я немного сократил дерево чтобы не дублировать большой кусок кода):



...
[ "block",
[ "s", "\n " ],
[ "declaration",
[ "property", [ "ident", "position" ] ],
[ "propertyDelim" ],
[ "value", [ "s", " " ], [ "ident", "absolute" ] ]

],
>>>[ "declDelim" ],<<<
[ "s", "\n" ]
...




После того как отработали все плагины, c помощью того же gonzales-pe AST превращается обратно в css код,

и забытая точка с запятой оказывается на своем месте:

.block
{
position: absolute;
}




Как начать это использовать ?




Шаг первый



Нужно тем или иным способом добавить CSScomb.js в зависимости своего проекта.

В случае если на проекте используется npm, нужно добавить его в devDependencies в package.json

{
...
"devDependencies": {
...
"csscomb": "2.0.4",
...
}
...
}




Шаг второй



Чтобы плагины приводили код к тому виду, который нам нужен, в корень проекта нужно положить файл с конфигурацией плагинов. Подробно о том, что делает каждый плагин, и какие опции у него есть, можно почитать тут.

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



csscomb -d example.css > .csscomb.json




Таким образом могут быть сконфигурированы все плагины. Исключение составляет только сортировка свойств, которую по-прежнему придется писать самому, либо скопировать откуда-нибудь руками.
Шаг последний



Нужно сделать так, чтобы CSScomb.js в режиме линтера запускался перед коммитом наравне с jscs/jshint-groups или другими инструментами, которые обычно запускаются перед комитом. В случае использования git и linux/BSD он может выглядеть примерно так:

#!/usr/bin/env bash

PATCH_FILE="working-tree.patch"
NPM_BIN="./node_modules/.bin"

function cleanup {
exit_code=$?
if [ -f "$PATCH_FILE" ]; then
patch -p0 < "$PATCH_FILE"
rm "$PATCH_FILE"
fi
exit $exit_code
}

#Прибираемся при выходе из скрипта
trap cleanup EXIT SIGINT SIGHUP

# Создаем патч
git diff > "$PATCH_FILE" --no-prefix
# Сбрасываем не застэйдженный изменения
git checkout -- .

# получаем список файлов в которых были изменения, которые мы хотим закоммитить
git_cached_files=$(git diff --cached --name-only --diff-filter=ACMR | xargs echo)
if [ "$git_cached_files" ]; then
#Собственно натравливаем CSScomb.js
$NPM_BIN/csscomb -v -l $git_cached_files || exit 1
fi




У этого хука есть интересная особенность. Казалось бы, почему не запускать CSSComb.js сразу в режиме исправления и затем автоматически молча коммитить? В большинстве случаев это действительно нормально работает, но проблемы возникают в ситуации, когда мы делаем несколько правок в файле, а закоммитить хотим только одну из них (git add -p). В этом случае если мы находим ошибку в той версии файла которую коммитим, начинаются неприятные ситуации:


  1. Мы можем запустить CSScomb.js на ту версию файла, которую собираемся закоммитить, и в некоторых ситуациях получить конфликты при наложении патча.

  2. Либо запустить CSScomb.js на текущем файле целиком, но в правках, которые мы пока не хотим коммитить, может быть что угодно, включая неправильный код.




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

Готово!




Всё. Теперь CSScomb.js не даст закоммитить код не по кодстайлу, и даёт возможность в одну команду привести код к нужному виду.

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

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


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.


Комментариев нет:

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