Приводим в порядок css-код. Опыт Яндекса
Всем привет! Я работаю над фронтендом огромного проекта — поисковой выдачи Яндекса. И у нас, как и у любого другого большого веб-проекта, есть огромное количество 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 1 px rgba (0, 0, 0, .1); -moz-box-shadow: 0 0 0 1 px rgba (0, 0, 0, .1); box-shadow: 0 0 0 1 px rgba (0, 0, 0, .1); z-index: 2; position: absolute; height: 2 px; width: 2 px; 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: 2 px; height: 2 px;
color: #fff; -webkit-box-shadow: 0 0 0 1 px rgba (0, 0, 0, .1); -moz-box-shadow: 0 0 0 1 px rgba (0, 0, 0, .1); box-shadow: 0 0 0 1 px 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, либо законтрибьютить нужные штуки самостоятельно.