How we refactored our CSS for BGP

Sharlene Wong
Government Digital Services, Singapore
7 min readSep 16, 2016

In my earlier article, I described why our team began to embark on a journey to refactor our SCSS code. Let’s take a look now at how we went about it.

Something had to be done to our CSS. But what?

We first heard about PostCSS at Singapore’s CSSConf 2015. Then, we decided to build our landing page in PostCSS in a tech spike. This led us to realize how great local CSS was for maintainability, and how easy it is to use together with React.

import styles from './news.scss';export default class NewsPage extends Component {...}

However, we estimated that the amount of effort needed to convert existing SCSS code to local CSS was going to be significantly greater than the effort needed to refactor existing SCSS. There also wasn’t really any other “push factor” for us to leave SCSS.

So, we decided to focus on refactoring existing SCSS instead of converting everything to PostCSS.

So, we decided to focus on refactoring existing SCSS instead of converting everything to PostCSS.

Architecture

First, we explored the popular CSS architectures out there: Scalable and Modular Architecture for CSS (SMACSS), Block, Element, Modifier (BEM) and Object Oriented CSS (OOCSS).

In OOCSS, a CSS object is defined to be “a repeating visual pattern that can be abstracted into an independent snippet of HTML, CSS and JS.We have been building our UI in React, and our React components encapsulate CSS.

So essentially… we’ve been doing a version of OOCSS all along! We were just never deliberate about “separating structuring from skin” and “separating container and content.”

Therefore, it really only made sense for us to formally adopt OOCSS, which ties in nicely with our existing React architecture.

Therefore, it really only made sense for us to formally adopt OOCSS, which ties in nicely with our existing React architecture.

Naming Convention

We standardised the way we name CSS classes. The first part of the class contains the name of the Page or Component. The second part describes the Element.

.dashboard-action-card {...}

For example, in .dashboard-action-card, “dashboard” refers to the Component, while “action-card” refers to the Element.

We also dropped the naming prefix “bgp” that we previously used to indicate that the CSS class was not from Bootstrap.

Our CSS classes also follow a hierarchy. We include terms inside the names to indicate the place of the CSS classes in our hierarchy.

Container > Wrapper > Group > Element

<div className="row dashboard-action-card-container">
<div className="dashboard-action-card">
<div className="dashboard-action-text-wrapper">
<h4 className="dashboard-action-title"> element </h4>
<p className="dashboard-action-description"> element </p>
</div>
</div>
</div>

Linting

Linting is always, always good. We practise linting for the rest of our tech stack, and it was high time to do it for SCSS.

We practise linting for the rest of our tech stack, and it was high time to do it for SCSS.

scss-lint is a natural choice, given that we do Ruby on Rails. It provides 60 linting rules as well as editor integration support. The only gripe we had was that scss-lint is not well-supported in RubyMine, the most widely-used IDE in our team (meh, we can live with this).

The team began by running through all 60 rules and deciding whether to switch on each of them.

For rules that we decided to switch on, we split them into four groups, then prioritised them for implementation:

  1. Important rules that might not break as much code
    We decided to first tackle rules that would give a high “Return On Investment (ROI)” — rules that do not require major effort to implement, but would significantly help to clean up the codebase.
  2. Rules that we can implement ‘automatically’
    These were rules that we could automatically implement (versus manually) using stylefmt, a tool that automatically formats CSS.
  3. Important rules that will break a lot of code
    We identified 3 rules in this category: NestingDepth, SelectorDepth, QualifyingElement. Almost every CSS file would break at least one of these rules.
  4. Rules that we want to implement, but later
    These were rules that we eventually wanted to implement, but were not as important to enforce immediately.

Test-Driven Refactoring

In order to ensure that we do not accidentally break the visual look of the site in the midst of refactoring, we decided to go for a test-driven approach. We did two tech spikes to decide on the testing tool to use.

Tech Spike 1: Page-regression gem

We did a tech spike with the page-regression gem, which performs regression testing of web page renders in RSpec. It does so by providing an RSpec matcher that compares the test image to an expected image that was previously captured. We ran the gem on the Dashboard page.

Expected image
Test image
Differences between test and expected images

Here’s what we have learnt:

  • page-regression only works with the Poltergeist driver, which was slightly troublesome for us because we use Webkit in our RSpec.
  • Data seeding cannot be random, or else the tests will fail. (We use FactoryGirl to create seeds.)
  • The size of test images captured seems to vary on different machines, despite us having specified the size of the image to capture.
  • Sometimes, the size of test images captured changes on the same machine.

However, we could not really solve the issue of the changing size of the captured images across different machines, and even on the same machine. Meanwhile, we tried something else.

Tech Spike 2: Applitools eyes

We did a second tech spike on Applitools eyes, an automated visual web testing tool recommended by one of our Quality Engineers (benefits of a cross-functional team!). The tool has proven to be a good one so far for the following reasons:

  • It is a browser plug-in, and it is easy to use.
  • We can see a history of tests that has been run before, allowing us to easily trace our steps.
  • We can mark out an area of the page for the test to ignore. This allows us to continue using random data seeding for our RSpec.
Applitools: The translucent blue boxes are areas where the test ignores. The pink highlights are where visual discrepancies occur.
Left: Expected image | Right: Test image

So Applitools it is!

Applitools was definitely a lot more reliable than page-regression gem, and it was easy to use. Hence, it became our choice of visual testing tool.

Best Practices for CSS/SCSS

Here are best practices for CSS/SCSS that we started following:

  • If the mixin has no argument, write it as a placeholder instead. Otherwise, we would be duplicating chunks of code (in the form of the mixin) across the file.
  • Completely avoid using universal selectors.
  • Avoid over-qualifying elements.
  • The browser parses CSS selectors from right to left. Thus, use descendant selectors as sparingly as possible.
  • Nest only when necessary. Otherwise, use indentation to indicate class hierarchy:
.dashboard-container {...}
/* Indentation */
.dashboard-action-card-wrapper {
/* Nesting */
.dashboard-action-card {...}
}

Cleaning up the codebase

Our previous article mentioned problems with our CSS. Here‘s a list of things that we are doing to rectify them:

  • Remove all unused classes and font stacks
  • Implement a Z-index scale
  • Standardize media queries
  • Remove unused colours
  • Standardize pixel units

We also decided to organize our CSS files into the following folder structure:

|_ stylesheets 
|_ animations
|_ base
|_ components
|_ resuable
application.scss

animations contains our CSS animations.

base contains the CSS of components that are used across a majority of our application.

components contains the CSS of components (as defined by React).

resuable contains the CSS of things that can be reused throughout the CSS codebase, such as mixins and functions.

Reflections

What would we have done with our CSS, if we could do it all over again?

  • Be deliberate about following good CSS practices. CSS is code, and when any code is left “unchecked”, it can grow into an insurmountable mess.
  • Implement PostCSS instead to make maintenance a whole lot easier.
  • Define our own custom framework instead of applying Bootstrap. We ended up writing too much code to get around Bootstrap’s constraints.

Nonetheless, the CSS refactoring exercise was still worth going through. What came out of it was a growth in front-end development capabilities in the team — and a stronger, more communicative and united team. We are now better positioned to tackle front-end development for any future products.

What came out of it was a growth in front-end development capabilities in the team — and a stronger, more communicative and united team.

Once we are done refactoring, it would be interesting to find out how much file size reduction the stylesheet would undergo, and how much the site loading speed would improve as a result.

Our refactoring process is far from over, but this marks the end of our sharing for now. We hope that it was interesting for you :)

Read: Why we had to refactor CSS for BGP.

Postscripts

P.S. Here’s a special shoutout to my fantastic teammates — Alvin, Kia Hwee, Guoyou, Wendy and Shermane — who came together to tackle our monstrous CSS refactoring!

P.P.S. We are hiring :) Shoot us an email at hiring[at]ida-gds.com if you’re interested.

--

--