Getting CSS Code in Order: Yandex Experience
Web DevelopmentHey there,
I’m currently working at the front-end of a huge project – Yandex search results. As any other web project, we have a huge amount of CSS code and a considerable team interacting with it.
When a lot of people write and edit CSS code with the help of various tools, it can become really complex and inconsistent. For instance, someone prefers writing vendor prefixes in one order, the other one prefers writing it in a different way. Some of them will use brackets round url, others won’t. When fixing some bug, someone could write, for example, position: relative at the beginning of the properties block without noticing that there’s already position: absolute somewhere between color and box-shadow. And he will keep wondering, why it’s not working.
But despite the fact that we write the code in different ways, there’s a perfect order in our repository: CSS code is absolutely consistent and looks great. The whole of it.
In this article you will read about the ways to achieve it.
First Steps
If we want CSS code to be written according to the code style, the first thing coming to mind, is some big boss to say: “Write it this way and strict to that” and then detect mismatches during the code review. Sounds great, but not in practice.
- It’s hard for a person to memorize the strictly recommended order of all the CSS properties and never make mistakes;
- Code review becomes a set of updates according to the code style, while we would like it to contain discussions about logic and architecture;
- All bugs should be fixed manually. All mismatches should be specified in some document.
Anyway, it does not look good, takes plenty of time and frustrates developers.
To satisfy developers, we had to look for another solution. And we’ve found it!
A Bit about Robots
There used to be an old robot — CSScomb, that solved the most important part of our task. It sorted properties in a specified order. But it also had some drawbacks:
- It could not do anything except the sort, though we wanted it to remove brackets when needed, sort vendor prefixes in ascending order;
- It was written in PHP and regular expressions and did not provide any abilities to be extended.
Though PHP is more popular than C++ in the sphere of JS programmers, it’s not the perfect solution.
Therefore, a member of our team has decided to create a tool without these drawbacks. A tool that could execute plenty of useful things, would be written in JS and could be easily extended. Despite the fact that Yandex employees take an active part in development, CSScomb.js is an open source project that has gone far beyond its limits.
Please, welcome, CSScomb.js – a new version of the old CSScomb that can perform plenty of cool things.
How does It Look Like?
Let’s take a look at the current development process by an example. Suppose we want to commit the following code for purely educational purposes:
.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;
}
It does not really look good, and is not even formed according to the code style. Thus, when trying to commit, we will see the following:
% git commit block.css -m "Add My awesome css"
Code style errors found.
! block.css
We will not be able to commit such CSS file, since there’s a pre-commit hook in git repository that checks all changes of CSS files with the help of CSScomb. It operates in linter mode. The hook is quite smart and checks only the files we are going to commit, not all of them.
At seeing this message, we do not get upset, but ask CSScomb.js to fix everything:
% csscomb block.css
Well, it’s a different story now:
.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);
}
It has done quite many of small fixes in the code. Transferred a bracket to another line, switched the color to the lower case, sorted vendor prefixes in ascending order and sorted properties in the correct order. Now this code will pass the automatic check successfully and will move on to further review of its functional qualities.
Thus, a developer does not have to think about the ways of writing CSS properly according to the code style. He just writes it the way he likes. All other things happen automatically. That’s exactly what we aimed at.
Structure
Such system consists of two parts: CSScomb itself and the pre-commit hook that uses it. I’ll tell you about the hook in the next part. Now, let’s review the CSSComb.js structure in details.
There are two things at the heart of CSScomb.js: a plug-in system and gonzales-pe CSS parser. The latter is a really cool thing that can operate not only with pure CSS, but also with preprocessors like Sass or LESS.
Let’s begin with the parser. It is applied on CSS code and builds AST on the basis of it. For example, for
.block
{
position: absolute
}
AST will look like the following:
[ "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" ]
]
This format is very lengthy and is not really comfortable for reading, but it’s good for the automatic processing that is executed by plugins. They are in charge of all further CSS conversions and operate not with the CSS code directly, but with the AST that is much simpler to handle. A separate config file has all the information as for the plugins to be used and their settings.
In the mentioned above example we did not use a semicolon after absolute. The plugin, which is responsible for detecting and fixing the semicolons, noticed and fixed it via AST modification (not to duplicate a big part of the code, I shortened the tree a bit)
...
[ "block",
[ "s", "\n " ],
[ "declaration",
[ "property", [ "ident", "position" ] ],
[ "propertyDelim" ],
[ "value", [ "s", " " ], [ "ident", "absolute" ] ]
],
>>>[ "declDelim" ],<<<
[ "s", "\n" ]
...
Once all the plugins have done their job, AST converts back to CSS with the help of gonzales-pe, and the missed semicolon turns out to be in a proper place:
.block
{
position: absolute;
}
How do We Start Using It?
The First Step
Add CSScomb.js to the project dependencies. If npm is used, add it to devDependencies in package.json:
{
...
"devDependencies": {
...
"csscomb": "2.0.4",
...
}
...
}
The Second Step
To make plugins normalize the code to the form we need, place a config file in the project root. Here you will find the details of each plugin options.
CSScomb.js can do it automatically to some extent, as it can generate the config by using some existing CSS as the basis.
csscomb -d example.css > .csscomb.json
We can configure all plugins in a similar way. The only exception is properties sort. We will have to either write it ourselves, or copy from somewhere.
The Last Step
We should make CSScomb.js to be launched (in a linter mode) before the commit on equal terms with jscs/jshint-groups or other tools that are usually run before the commit. In case of git and linux/BSD use, it can look something like this:
#!/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
}
#Cleaning up before exiting the script
trap cleanup EXIT SIGINT SIGHUP
# Create a patch file
git diff > "$PATCH_FILE" --no-prefix
#Flush the unstaged changes
git checkout -- .
# get a list of files (containing changes) that we want to commit
git_cached_files=$(git diff --cached --name-only --diff-filter=ACMR | xargs echo)
if [ "$git_cached_files" ]; then
#Set CSScomb.js
$NPM_BIN/csscomb -v -l $git_cached_files || exit 1
fi
This hook has an interesting peculiarity. Why not to run CSSComb.js in the correction mode at once and then commit automatically? In most cases it does work. But problems will turn up, when there are several corrections in a file, but we want to commit one of them (git add -p) only. In this case, if we find a mistake in the file version to be committed, the following unpleasant situations will take place:
- We can run CSScomb.js for the file version to be committed and sometimes get conflicts when applying the patch;
- Or, you can run CSScomb.js on the current file. But there can be anything in the updates we do not want to commit for now, including the wrong code.
It’s annoying when a file completely changes itself. Therefore, we think that developers specifically should start the CSScomb.js.
Done!
That’s it. Now, CSScomb.js will not allow to commit bad CSS, which allows to normalize the code to the necessary format with the help of one command only.
That’s how a simple idea and not a simple tool help to keep the code in order. They also save developers’ time and nerves.
We can use it in many projects. If you’re missing some basic functionality or plugins, you can always add issue, or contribute the necessary things by yourself.
Comments
Federico Ramirez
Jon Green
Federico Ramirez
Vinay Raghu
Vinay Raghu
Kukuruku Hub