Wildbit

The Blog

Thoughts on building web apps, businesses, and virtual teams.

16 Apr Refactoring >14,000 lines of CSS into Sass ← Go back

Posted by Eugene Fedorenko on April 16, 2012 — 40 Comments

Beanstalk is a mature product and during its’ 5 years of existence the design and UI have been changed a lot. Our CSS grows accordingly and lately it consisted of 5 files, 14,211 lines and 290 KB of code. We handcrafted our CSS from the start but more recently it had become quite hard to manage. With the growth of new tools like Sass and LESS I decided to rebuild our stylesheets into something cleaner and easier to maintain, and after working with the new system for a month I’m really happy with the results.

Our previous CSS organization

Keep in mind, Beanstalk was launched back in the days when Internet Explorer 7 was the latest version, conditional comments weren’t used much and mobile versions of applications barely existed. Since then our CSS had a few major refactorings and cleanups in addition to day-to-day maintenance, so it was in quite good shape, but the file structure still reflected these historical milestones:

  1. global.css — main stylesheet of the application
  2. print.css — print stylesheet
  3. mobile.css — mobile stylesheet with media queries, added last year
  4. ie.css — hacks and workarounds for IE 7 (we dropped IE 6 support in Jan 2010)
  5. ie8.css — hacks and workarounds for IE 8

Internet Explorer stylesheets are loaded with conditional comments, so most of our users download only the first 3 files.

The problems with handcrafted CSS and multiple files

Our old CSS organization had two conflicting problems:

  1. It’s hard to collaborate on a 252 KB behemoth, which was our global.css. Merges are problematic and tools like blame can get slow.
  2. It’s easy to forget about mobile and print stylesheets when they’re in different files.

CSS itself had a few issues and spots for potential improvement, too:

  • Ghost selectors from non-existing UI elements.
  • Missed standard CSS3 properties and forgotten vendor prefixes.
  • Slow CSS selectors.
  • Redundant rules and validation errors.

Some of these problems were easy to fix with CSS preprocessors. I decided to go with Sass as it’s a more popular choice and ships with Rails 3.1. It was hard to choose between it and LESS as they’re both fantastic products, but I had to settle on something.

Getting Organized with Sass

I decided to go with a modular approach to CSS organization — every independent set of rules stored in it’s own file, together with related print and mobile styles. Based on Sass naming convention all partials start with underscore, and I grouped related partials together:

  • _integration.overview.scss
  • _integration.profile.scss
  • _integration.wizard.scss

I ended up with 56 partials for our styles and two additional files for variables and mixins. They all imported into global.scss, which is converted to CSS afterwards. As I prefer expanded output style and by default Sass uses nested, I prepared a simple Shell script to launch Sass, so my teammates can easily produce correctly formatted file:

sass --watch stylesheets/scss/:stylesheets --style expanded

sass.sh is stored in the repository next to stylesheets and can be run with a simple command (I run it with an Alfred script):

sh sass.sh

It’s important to keep variables and mixins in partials as they can be used in other files, like in IE stylesheet. Another good example of variables and mixins sharing is our administrative area — it’s stored in separated directory in design repository, but it’s design is based on application’s global.css with some additions. It’s styles and widgets are stored in admin.css, which overwrites some global rules and uses application’s variables and mixins:

  • administration
    • stylesheets
      • scss
        • admin.scss
        • ↪ _variables.scss
        • ↪ _mixins.scss
      • admin.css
      • ↪ global.css
  • application
    • stylesheets
      • scss
        • global.scss
        • _variables.scss
        • _mixins.scss
      • global.css

To import partials from another directory I use the load-path option:

sass --watch stylesheets/scss/:stylesheets --style expanded --load-path ../application/stylesheets/scss

(Later we’re going to just build admin.css from used partials without importing global.css at all.)

Steps to turn our CSS into a thing of beauty

After taking our CSS apart into multiple partials I started the cleanup and light refactoring process.

Improving color management with Variables and Functions

Sass variables are great for storing colors and font families. I actually really like to know where my colors are coming from, so I rebuilt some color values with Sass functions like darken and lighten. At some point I even converted a bunch of colors to RGBA just because they were so much easier to understand, but then decided that providing fallback RGB colors is too much hassle. This made me think — what I really wanted is not transparency of RGBA, but a way to generate one color from two with some transparency. For example, our tabs are white with 12% (normal state) or 31% (hover state) transparency over brown:

Beanstalk tabs

Turns out there is no built-in function for this calculation in Sass, so I had to write it myself — check it out on GitHub. It’s really easy to use — function blend(#534B38, rgba(#FFF, .12)) generates #67604F, but I recommend using it with variables for even better obviousness — blend($brown, rgba(white, .12)). It must be my most used color function in Sass.

Removing Ghost Selectors with Regular Expressions

Well, it happens — you redesign some element and forget to clean up old CSS afterwards, which makes the code bloated, increases file size and decreases performance. I spent a day checking if most of our CSS selectors existed in design mockups or development code. Consistent code formatting and simple regexps come in handy here. I used these ones the most:

  • [" ]classname — is this class name ever used?
  • h3.*classname — is this class name ever used with h3 element?

With these checks and refactoring I got rid of about 10% of our stylesheets, which is much more than I expected.

Consistent Vendor Prefix Handling with Mixins

Writing all vendor prefixes in a consistent way could be tiring and painful, so it was no wonder that I found lots of places where standard property or some existing vendor prefix was missed. I even found a dozen places where we used -khtml prefix, while we don’t really support Safari 2 or Konqueror. This problem is easy to solve with Sass mixins, so I created a bunch of them for the most popular rules. Now I can use one-line includes instead of 3-4 lines of vendor prefixes:

@include border-radius(3px);

generates these styles:

-webkit-border-radius: 3px;
   -moz-border-radius: 3px;
        border-radius: 3px;

Although Compass has a great collection of pre-build mixins I decided to start with our own toolkit and extend it as we go.

Seek and Destroy any slow CSS selectors

This topic is really out of scope of this article, so I’ll just recommend a couple of good starting points:

Our CSS is not the fastest one out there yet, but this time I got rid of inefficient universal (*) and attribute ([attr='attr']) selectors. There is more we can do to speed up our rendering, but it’s a big task on it’s own and we’re working on it separately.

Remove redundant rules and validator errors

dl dt { 
    display: block;
    float: left; 
    vertical-align: bottom;
}
hr { 
    margin: 0em 0em 1em;
}
ul li { 
    display: inline; 
    width: 50px;
}

What’s wrong with all of these styles? They are redundant — floating element is always a block and vertical-align doesn’t apply to it, zero doesn’t need units and width or height don’t apply to inline element. These are just a few examples of possible code bloat and you can find more with CSS Lint and thoughtful code review. It’s always a good idea to check CSS with W3C validator, too — I found a few misspelled properties and values, even while I expected our code to be perfect. (Ha!)

Goodbye IE7 support

We dropped IE6 support two years ago when only 0.76% of visitors of our landing site were using it. Today Internet Explorer is used even less — only 0.1% of our visitors use version 7, 0.7% use version 8 and 1% use version 9. Hey, the total number of our IE visitors is getting really close to a number of our mobile users! We decided to drop IE7 support, but for some time we’ll still provide a file with hacks we built before.

The Result: High Performance CSS

What about front-end loading speed?

  • HTTP requests reduced from 3 to 1 (from 4 to 2 with IE 7-8).
  • File size reduced by 14% (or 38% when using the compressed output style).
  • Pages get loaded 0.5-1s faster.

Not bad as for a byproduct of code refactoring, is it?

But what’s even more important is that my performance is much better after all these changes. Our code is in much better shape. I have to write much less code thanks to mixins. We have a much cleaner merge process, as only changed Sass partials get merged. That said, there is one downside — now I terribly want to convert the rest of our sites and apps to the same system!

Discuss this on HackerNews

40 Comments

Has beanstalk rolled this CSS out to the live site yet?

Brad — April 17, 2012, 12:43 pm

Brad, we did. It was released two weeks ago.

Eugene Fedorenko — April 17, 2012, 12:49 pm

I like the part (a lot) on where you cleanup the CSS. It happens to us all, we create something, we then make some changes, and we don’t need that CSS class anymore. I think that the waste in any CSS file is anywhere between 20% to 80% of the all the CSS file.

Fadi El-Eter — April 17, 2012, 1:15 pm

So you prefer SASS versus LESS? Any reason? I started using LESS on a project and it’s taking some time to getting used to but man I do like it.

Furthermore, the redundant stuff, I think we are all guilty of this one. But man oh man, 14k lines of code..lol

Marco Berrocal — April 18, 2012, 12:18 am

Sounds like your “global” contained a whole lot of shit that wasn’t a) global or b) reused that much in the site it should of been classed as global.
Your win has not a lot specifically to do with modularisation and SASS but cleaning shit up.

IMHO, large sites should contain a global stylesheet that contains the base styles (fonts/resets/basic styles/styes for the wrapper template and common classes) then individual sheets per-page. If you find its being reused it probably belongs in the global.
The BEM methodology works in a similar fashion by splitting out css into modular chunks, assuming you ignore all the templating crap people keep adding in.

Chris McKee — April 18, 2012, 2:27 am

Thanks for the insight, Eugene! I recently started with LESS and really love it so far. I still have to use it at a bigger project and check out if it also satisfies my needs there, that means if I can live without proper debugging (with Firebug) and if the file size of the compiled CSS file isn’t too big and bloated. But I’m very confident that things turn out pretty well.

Christian Krammer — April 18, 2012, 3:02 am

Marco, it wasn’t easy to choose between LESS and Sass – they’re both great tools. I decided to go with Sass (I use SCSS syntax) as a more mature and popular tool, plus later I may want to bring Compass to the toolkit. The same result can be achieved with LESS, too.

Eugene Fedorenko — April 18, 2012, 6:02 am

Chris, it’s a two-sided problem – individual per-page stylesheets bring extra HTTP requests, but bigger CSS makes rendering slower. I think the only way to compare performance is to implement individual stylesheets, run tests and compare loading time. This is actually much easier now with Sass partials, so we may want to do this.

Eugene Fedorenko — April 18, 2012, 6:13 am

Christian, maybe I miss the point, but Firebug (or Webkit Inspector) is actually your only way to debug compiled CSS. I think that’s okay.

Speaking of file size, I’d say compiled CSS is smaller than hand-written, as it doesn’t include comments and extra spaces. And of course it’s a good idea to use compressed output for production environment.

Eugene Fedorenko — April 18, 2012, 6:19 am

How do you trace back a line of scss to the partial when inspecting in firebug?

michael arcement II — April 18, 2012, 7:53 am

Michael, both Firebug and Inspector show full selector, so I either search for it (if I’m not sure in which partial it could be) or use Sublime’s Goto Anything tool to navigate to it in open file. I use this approach for a long time, so I didn’t feel any difference after switching to Sass.

Eugene Fedorenko — April 18, 2012, 8:01 am

@Michael – There is also an extension for firebug called FireSass that will give you the original line number from the scss file when you inspect it.

Congrats to the Beanstalk team. We’re happy customers at my full time employer and working on a conversion to Sass as well.

Ethan Gardner — April 18, 2012, 9:35 am

Chrome inspector has a great tool for css rule usage – the “profiles” tab. This is useful to knock out the obvious selectors that are not utilized or only have a couple instances of use. You can also use it to make sure your top/frequent selectors are optimized.

Great read!

Paul Redmond — April 18, 2012, 12:01 pm

All that stuff for 0.5-1s gain?
Amount of hours you spent on that transition from css to scss is going to pay off for about 20 years man.

It totally didn’t worth it.

My 0.02$.

pentago — April 19, 2012, 11:14 am

It totally didn’t worth it.

1. A one second speed boost on a web page is super awesome and easily worth weeks of work. Not to mention the reduced request and file size (extra important on “mobile”).

2. There are huge immeasureable benefits to this. For instance, re-familiarlizing yourself with your entire code base can get you excited about your work again and dissolve fear about jumping in and fixing stuff. Just as important, future updates will be easier and things like CSS3 support will be spot-on.

3. That’s not a sentence.

Chris Coyier — April 20, 2012, 12:36 am

Eugene, I thoroughly agree with Chris McKee above, but you missed that because it’s SASS your per-page files can be partials or just imported (which will be inlined by SASS) so there are no extra HTTP requests.

Samuel Cochran — April 20, 2012, 3:42 am

Was the mix function [1] from SASS not the right one? 12% white with brown is “mix(white,brown, 0.12)“. I didn’t compared results to your blend function. Are there big differences?

[1] http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method

Haschek — April 20, 2012, 5:34 am

Samuel, it’s quite easy to create per-page CSS files with Sass combining global and page styles, but I’m afraid it’s not the most effective solution. Currently global.css is downloaded only once and then cached until next release (it’s fingerprinted), but with your approach user will have to download a few per-page CSS files after every release.

There is nothing wrong with this approach for sites with heavy traffic on one page though, like Facebook’s News Feed.

Every “best practice” needs a check with a stopwatch, so I plan to experiment with a few ways to serve CSS (only one file, global.css + per-page CSS, only page-per CSS) and will make sure to blog about performance results.

Eugene Fedorenko — April 20, 2012, 6:06 am

Haschek, you’re right! I tried this function before and didn’t get what I expected, but turns out I used it in a wrong way – (bg, fg, %) instead of correct (fg, bg, %). Thanks for correcting me! Going to replace ‘blend’ with ‘mix’ in my code.

Eugene Fedorenko — April 20, 2012, 6:23 am

I had a workflow hands to alter the CSS, a project in which the file is 13,000 lines of styles held for 4 days, I optimized the 1000 lines, a lot of work

Employers are often reluctant to use services and online tools that entail excessive costs

But I liked my job)))

ps. sorry my english low(

on my blog i’m working on the redesign (the experience of growing) so do not worry)

good post, thank you

JAH — April 23, 2012, 11:49 am

I’m working in a more-than-4k-lines-of-css-code project with LESS and it has around 80Kb . I tried to handle the output version just for test purpose but it has more than 115Kb. Man I can tell there is no better way to develop a huge project without these CSS preprocessors. By the way wish I could drop IE7 and even IE8 support… Lucky you! =)

Edson — April 24, 2012, 3:45 pm

‘border-radius’ is a bad example for a mixin that generates the various prefixed properties. All browsers have been supporting the unprefixed property for a long time already; there’s no need to prefix that one.

Gunnar Bittersmann — April 24, 2012, 6:25 pm

How do you trace back a line of scss to the partial when inspecting in firebug? I also want to know

Alice — April 25, 2012, 2:47 am

@Michael
@Alice
That’s a really good point and I had the same issue when starting to work with SASS.
I solved it starting to use Codekit[1] . I has an option (above a tons of others) that lets you print, in compiled .css, both line number then original file path of the partial that generated that snippet of code.
Neat.

It’s commercial but c’mon, if only one hour gained looking for bugs, it repaid itself.

@Eugene
Really interesting article, thank you for sharing, it’s a great source of “Read it later” readings for this weekend. ;)

1. http://incident57.com/codekit/

Giuseppe — April 27, 2012, 4:11 am

Gunnar, it means nothing that “all browsers have been supporting the unprefixed property for a long time”. -moz- prefix is the only way to get border-radius in Firefox up to 3.6, which is used by 9.1% of our users. This version was discontinued by Mozilla only a few weeks ago, and had regular security updates until March 2012.

Eugene Fedorenko — May 7, 2012, 4:43 am

Sorry, comments for this entry are closed at this time.

← Go back

Get in Touch

Wildbit, LLC

Work 20 North 3rd St, 701
Philadelphia, PA 19106 USA

Google Maps
 
 
Fax
+1 (267) 200 0835
Email