CSS Lint is a tool to help point out problems with your CSS code. It does basic syntax checking as well as applying a set of rules to the code that look for problematic patterns or signs of inefficiency.
Being the slow-adopter that I am, I quickly linked to it and then quietly filed that away for later perusal. With my recent re-factor of the Orin theme to switch from “straight Sass” to Compass, it occurred to me that the next logical step would be to analyze the code quality of the stylesheets. Was I really writing efficient selectors? Where was I still being “sloppy”? What mistakes was I making? I wanted to know, and I wanted to put my performance money where my mouth was. And I figured: CSS Lint can help me.
My Naïve Original Plan
I assumed that I could run CSS Lint against Orin’s main stylesheet, have a look at how bad it was, and then fix every complaint that CSS Lint dumped out.
So I did a little
npm install csslint and then a little
csslint style.css and I got a little:
csslint: There are 144 problems in style.css.
This was… way more than I thought there would be. I thought there might be 20-30. I thought there might be 50 at most. This was almost three times more “problems” than I expected to see.
After I collected my jaw from the floor and collected my wits (scattered, as they were, like a pugilist’s teeth all over the mat), I steeled my resolve for a deeper dive. If CSS Lint said that I had 144 problems, then I had at least that many problems to deal with.
My Revised and More Realistic Plan
At the outset, I still intended to “fix” all 144 “problems”. It seemed like this was something that could be accomplished. This was something that I could do.1 Like with any project, the strategy seemed easy: pluck off the low-hanging fruit to get a little momentum, and break the problems down into their high-level categories. The solution would present itself, or I would adjust my expectations and plans as I went along. If nothing else, I committed myself to getting it to a more reasonable number. I wanted to at least get it under the original “worst case scenario” of 50.
Thus did the intermittent hacking begin.
The CSS Lint Github repo has a good wiki page describing the rules that they use for determining the errors and warnings. I won’t list them all here,2 but needless to say, like any linter, you would do well to pay attention to the linter author’s agenda and what her idea of best practices are. As Nicole has stated in some of her talks: “CSS (much like JS) will let you do anything (no matter how stupid)”.34
When I started in on Orin’s style.css, I noticed that for the most part, my problems fell into a couple of big categories:
- Use of
!important. The “Jedi mind trick” of CSS. And/but if that is true, then we’re all Sith, because it’s a dirty trick.
- Headings. Qualified when they shouldn’t be. And (re-)defined over and over again.
- Universal selector (*). It’s slow. We know.
- IDs as selectors. Possibly the most controversial “rule” in the set. But one that I agree with in spirit…
-ms-transitionproperties. Which I blame on Compass.5 And then blame myself for not knowing they existed.
- Too many floats. They suggest a grid system. I suggest “no”.
- Too many font declarations. Which… well: maybe?
There were some other complaints in there (e.g., over-qualified selectors,
0 values with units…) but I felt those all fell under the rubrik of “low-hanging fruits”. Armed with the above in mind, I went to work.
For someone that is not particularly fond of writing CSS, I think I did OK overall when I decided to close my feature branch and merge it all back into master. If you’re interested, you can see the diff here. As for a high-level summary of what I accomplished:
- I went from 144 problems reported by CSS Lint and got it down to only 23. I may have begun this journey believing that 23 would be my starting place, but by the time I got it all wrapped up: 23 seemed magical.6
- The overall complexity was reduced. I find this one hard to quantify, but (a) I can feel it when I read the SCSS source files and (b) the compiled CSS went from 822 lines (uncompressed) to 734 lines.
- Reduced file size. It wasn’t a big reduction–we started at 17781 bytes and finished at 17753 bytes–but it was (technically) a reduction. I noted that there were some ways I could have tweaked things to get the file size smaller, but then I would be bringing the “problem” count back up.
And that was an interesting question to be asking myself there at the end: do I make this change to please the linter? or do I leave this declaration as-is to minimize file size? and/or complexity?
Comments and Observations
As I just mentioned, toward the end I felt like I was really getting into the land of diminishing returns and I was constantly asking myself about the trade-offs. This was how I knew that I had gotten it to a pretty good place. There was no question in my mind about doing the work to remove the
!important directives or the duplicated
transition properties or the selectors with 2 IDs in them;7 but I struggled with the decisions to re-factor the declarations for headings (which I did) and the unqualified attribute selectors (which I did not). Those two were the clearest examples, and so those are the two that I’ll use to frame my comments.
First: the headings. CSS Lint has a bunch of rules that surround the headings. In a nutshell, CSS Lint wants the declarations for your headings to look something like this:
Naturally, there is a rationale for this and it makes sense when you fully buy into the OOCSS methodology. I’m being honest here when I say: I agree with OOCSS in spirit, but one might also colorfully describe it as requiring a fanatical discipline.8 This is part of why I struggled with the decision to re-factor the headings. Ultimately I did, and it touched more than just the CSS, but I’m still not totally sure it was the right choice.9
Having the style for each heading element defined only once comes with a cost. If you’re using any kind of reset, then the chances are good that you’re penalizing yourself right out of the gate: your reset will almost certainly be applying
margin:0;padding:0; or more to the heading elements which you will then immediately re-define in the body of your main stylesheet with the CSS necessary for your site’s look-and-feel. The easy way out (from a linting perspective) is to put your reset in a separate file, but if that doesn’t make sense for you10 then you now have to deal with that feedback from CSS Lint.
You have 8 h1s, 7 h2s, 7 h3s, 4 h4s, 4 h5s, 2 h6s defined in this stylesheet.
So what’s the alternative? I bent on this one, but I felt silly having
margin:0;padding:0;font-family:... declared six times. And yes, I realize that there are other ways to accomplish some of these effects but at some point it becomes difficult to justify. In my case, I had 48 “problems” reported by CSS Lint about headings. So I took the brute force way out, but with the knowledge that there was a more elegant solution that would just have to wait.
On the other side, we had the unqualified attribute selectors. “Known to be slow.” This is fair, and again I understand the why of it, but for my money, there was no way around this one. In particular, I felt the pain on
input elements–form fields. The example of an “OK” selector pattern that includes attribute selectors has
.something [type=text] a which would be fine except for the part where the elements I’m styling, and thus selecting for, are inputs, and thus won’t have anything inside. Were I completely rolling my own site, the answer would be simple: add a class to the inputs and style based on the class.
Eventually we just wind up in the long-tail of optimization. Making a best-effort at unifying the heading declarations? I was OK with that. Writing PHP functions to intercept some-but-not-all11 of the form fields to apply classes to get them styled properly? Not on board. Eschewing all ID selectors? even for the single-serving widgets and plugins installed on the site? Not on board.
This was absolutely a great exercise to engage in, and I suggest that everyone with more than his or her toes in the CSS dev pool take a crack at this. You may be shocked at how many errors and warnings come up for your code; I know that I was
embarrassed . But you may also be surprised at the kinds of questions the linting leads you to.
Did I expect it to complain about IDs? I did. Did I expect it to complain about a selector with two IDs? I did not.
Did I expect it to complain about
!important? I did. Did I expect it to be trivially easy to re-factor those
!important directives out? I did not.
Did I expect it to complain about multiple heading definitions? I did not. Did I expect myself to take a good hard look at the way I was deploying
h3 tags throughout the site? I definitely did not.
My thanks to Nicole and Nicholas for bringing CSS Lint into the developer community. This exercise got my wheels turning in many ways and I urge you to let it turn yours as well.
UPDATED: (2/16/2012) If I’m interpreting issue #240 (as it arises from the comment thread on #218) correctly, then it seems that applying the
input to the attribute selectors is sufficient to qualify them. This would be a coup, as it would eliminate 16 of the remaining 24 “problems” in my stylesheet — meaning that (after accounting for the other erroneous error) we are down to a semi-respectable 7 problems.
UPDATED: (2/17/2012) Looks like there’s also an open issue/thread about the duplicate headings issue (and duplication in a more general sense) tracked here on issue #80. How did I miss that?
- Or so it seemed. [↩]
- You can read, can’t you? You’re reading this. Read that, too. [↩]
- Source: Nicole Sullivan’s E4H CSS Summit presentation (July 2011). [↩]
- However: to the Compass team’s credit: when I added in the
-ms-transitionproperties and checked those bad larries in IE9? Can you guess what didn’t “transition”? [↩]
- Technically the final reported number was 24. But one of those “problems” was actually a bug in parser. See also this and this. [↩]
- Seriously. I had like… 3 of those. So embarrassed. [↩]
- I think I just described it as requiring a fanatical discipline. [↩]
- And I eventually went back and “undid” that good work for one special case anyway. [↩]
- And I say that because having a separate CSS reset file doesn’t make sense for me. [↩]
- And thus not enough. [↩]