Содержание
Всем привет!
Я работаю над фронтендом огромного проекта — поисковой выдачи Яндекса. И у нас, как и у любого другого большого веб-проекта, есть огромное количество css-кода и немаленькая команда, которая с ним взаимодействует.
Когда много людей, используя разные инструменты, пишут и редактируют css, со временем этот css может получиться очень запутанным, неконсистентым и в целом начинает выглядеть плохо. Например, кому-то удобнее писать вендорные префиксы в одном порядке, кому-то — в другом, кто-то ставит кавычки вокруг url, кто-то — нет, а кто-нибудь фикся срочную багу к релизу мог бы, к примеру, написать position: relative
в начале блока свойств, незаметив что где-нибудь внизу между color
и box-shadow
, уже есть position: absolute
, и долго гадать, почему у него ничего не работает.
Но несмотря на то, что все пишут код по-разному, у нас в репозитории идеальный порядок: css-код полностью консистентен, и прекрасно выглядит. Весь.
Как мы этого добились, можно прочитать под катом.
Первые шаги
Если мы хотим, чтобы весь css писался по кодстайлу, то первая очевидная мысль — это чтобы какой-нибудь самый-главный-начальник сказал: «Надо писать так-то и так-то, соблюдайте», а несоответствия отлавливать на код ревью. Звучит круто, но, к сожалению, только звучит, а на практике получается так себе:
- Все мы люди — а человеку немного сложно запомнить точный-рекомендованный-порядок всей кучи css свойств, и никогда в нем не ошибаться.
- Код ревью в таком случае превращается в набор правок по кодстайлу, а хотелось бы чтобы в нем обсуждали логику и архитектуру.
- Все ошибки в этом месте нужно исправлять руками, а все несоответствия уточнять в какой-нибудь доке.
В общем, выглядит как-то не очень, отнимает кучу времени и, что самое главное — очень расстраивает разработчиков.
Чтобы разработчики ходили довольные, пришлось искать какое то другое решение, и оно было найдено.
Немного о роботах
Был один старый робот — CSScomb, который решал самую важную часть нашей задачи:
он умел сортировать свойства в заданном порядке. Но у него было несколько недостатков:
- Кроме этого он не умел ничего, а ведь очень хотелось и кавычки где не надо убирать, и вендорные префиксы лесенкой выстраивать…
- Был написан на 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
). В этом случае если мы находим ошибку в той версии файла которую коммитим, начинаются неприятные ситуации:
- Мы можем запустить CSScomb.js на ту версию файла, которую собираемся закоммитить, и в некоторых ситуациях получить конфликты при наложении патча.
- Либо запустить CSScomb.js на текущем файле целиком, но в правках, которые мы пока не хотим коммитить, может быть что угодно, включая неправильный код.
Да и в целом это не очень приятно, когда файл неожиданно сам по себе меняется. Поэтому мы пришли к выводу, что CSScomb.js должен запускаться самим разработчиком.
Готово!
Всё. Теперь CSScomb.js не даст закоммитить код не по кодстайлу, и даёт возможность в одну команду привести код к нужному виду.
Вот так простая в общем-то идея и несложный инструмент помогают держать код в порядке, а также экономят время и нервы разработчиков.
Такую связку можно использовать во многих проектах, а если вам не хватает какой-либо базовой функциональности или плагинов, то всегда можно завести issue, либозаконтрибьютить нужные штуки самостоятельно.