Front End Architecture At Punters.com.au

When I joined Punters in March 2015, I was the first dedicated front end developer brought onto the team. The other developers on the team were full-stack. We are a small team, but the company was growing and it was evident that a new architecture was needed in order for the front end to scale easily. There were a couple of things I needed to keep in mind while coming up with a solution; due the data-heavy content on punters, our site was not responsive, we had different sites for mobile and desktop which meant we had to manage code for both sites, we also have multiple brands which share some of the same code.

At Punters, we work in an agile environment, our tasks get broken down into small components, and we release updates up to 20 times a day. This is great as a developer because you are seeing your work on site within hours of completing it. However, with styles spread across many areas we needed a way to keep styles consistent.

File structure

One of the first things I learned while studying web development was that file structure is important for any project. Generally, once the project is first set up it will stay the same for the rest of the time the website is on-line. That was not the case at Punters. When I started at Punters the file structure looked something like this:

Our site was becoming component driven, with data and templates having a one to one relationship. The way our file structure was set up made it difficult to find the code, with naming conventions changing between CSS, and JS files, or sometimes you’d even find code added to the end of a page file. Our code base became bloated with unused code.

After a bit of research, I suggested we update the file structure to something more in line with this:

All code related to each brand would be moved into its own folder, and any shared assets would be inside the shared assets folder, which lives at the same level as the brand. Then inside our brand, we have our theme; which contains all of our CSS/JS used across the site. Pages, which contains our main PHP route file for the page. Each page has it’s own mobile and desktop folder inside, which contains the CSS, PHP and JS for the page. Finally, our component folder contains folders named corresponding with the API function name, which has the mobile and desktop versions of the mustache, CSS and JS.

This way we keep all elements of  components in one place. Then they are imported onto the pages it is used on. This allows us to localise CSS to a given component and get the performance benefits of only using that CSS when it’s needed. Any CSS needed for a specific page would be added to the page file.

Naming convention

The new file structure was a huge part of the architecture. But we still had a long way to go to reduce specificity and dependency within our code base. One of the next things I did was introduce a CSS naming convention. We decided on the BEM naming convention. Our DOM now had more classes. But this helped with our selectors. We went from over qualifying, heavily nested type selectors to easy to read class names that would be nested no more than three levels, and were not tied to DOM structure. 

Common styles

As with most websites, Punters would be reusing common styles, like margins and padding. For this we use spacing units in our variables, this gives some control back to the designer to be able to make changes and have it be a site-wide implementation, without having to change every page or component file.

For styles like buttons that would be repeated across components, I decided to use mixins. Using mixins allows us to have individual files for the component, but also allows us to make a change to common styles and have them update site wide. Our mixin file would contain something like this:

  
    
    .blackbook-button() {
      background-color: @greyDark;
      text-decoration: none;
      background-image: url('@{cdnDns}/icons/blackbook/black-book-ribbon-white.svg');
      background-repeat: no-repeat;
      background-position: 4px 4px;
      border-radius: 3px;
      color: @white;
      font-weight: bold;
      display: inline-block;
      padding: 3px 10px 3px 22px;

       &:hover {
        color: @white;
        opacity: 0.8;
        text-decoration: none;
      }
    }
  

Then usage is as simple as one line

  
  
    .top-runners__bb-btn {
      .blackbook-button();
    }

  

Working with the cascade

I recently learned about a CSS methodology created by Harry Roberts, called ITCSS. ITCSS uses the inheritance nature of CSS in our favour. Since learning about it I’ve spent a lot of time re-writing base LESS imports to reduce specificity of selectors, then organised the imports in order of specificity. This causes fewer re-paints from the browser, also it causes fewer headaches for the developers when they are not trying override core files.

Our specificity graph before introducing ITCSS looked like this:

Then after some refactoring and reordering of our base files it now looks like:

We now see a decrease in spikes, which appear later in the stlyesheet. This makes it a lot easier to work with the cascade without needing to use complex selectors, or !important to override core files.

Refactoring

We now have a good methodology moving forward, but there is still have a lot of legacy code to re-factor. Refactoring is a large part of reducing bloat and specificity within our code base. Rather than opening each file and refactoring the whole code base at once, we take a little bit of extra time to re-factor the code when working on projects. This means it will take a while before the code base is up to the new standards. Working this way means developers won’t be getting caught up in a refactoring tunnel and our users are still getting new features while seeing performance improvements constantly.

We’ve come a long way with our code base at Punters, but there is always room for improvement. We now have the foundation for easy to manage scalable code base. I look forward to watching it take on new forms and blossom in the coming months.