We had been working on refining our build process for awhile, so when the Lenovo project came in, we saw it as a great opportunity to level up our existing process. Coming off a day in the Sparkbox office with @snookca talking about SMACSS, we were all fired up to improve as much of our process as possible.
A few of us sat down and discussed how we would implement some of the new things that we were thinking about. The result was the process we used to build the templates for the Lenovo.com Australia site.
Gruntfile, which is checked into source control. This way, everyone on the project has the exact same process for building the website. This keeps everybody on the same page. If somebody updates the build process, everybody else gets that update.
The alternative to using a task runner like Grunt is to do all these tasks by hand. This means we’d be running many separate preprocessors to do all necessary preprocessing. While this wouldn’t be too bad with a single-person project, many people using many processors with many unique sets of configuration would likely cause consistency issues. Some people might use the SASS preprocessor and include Compass, wheras others would simply use the Compass preprocessor. These accomplish the same thing with slightly different results. Having everybody on a project generating code with the exact same tools and configuration is a solid way to prevent environment-specific bugs.
One of the first things that we wanted to address was how we were building our templates. Before this point, we had been writing HTML partial files, then primitively concatenating them together into full templates. This let us reuse the header and footer, and swap out the meat of the page to contain what we wanted. This method got the job done, but it didn’t really let us reuse the bits and pieces of the site in anything more than a copy-and-paste basis.
We had been looking to find better ways to build sites based on smaller modules that we could include when needed. Considering the size of the site we were about to build, being able to reuse these modules was critical to us. So we searched for a better solution.
This separation of structure and data is helpful when reusing code. Any bit of code that seems reusable can be put into a partial. These partials can be included by templates to build up a page. Swapping out the data let us use a single partial in multiple places, using different content for each instance. This allowed us to use this piece multiple times, but only maintain one file to make changes.
Another reason we wanted reusable partials was so that we’d be able to build a styleguide from these pieces. It was important for us to use live pieces of the site in the styleguide to prevent the styleguide from getting out of sync with the site. This meant we only had one codeline to maintain, with changes to this code reflected on both the site and styleguide.
We made an effort to follow a SMACSS-style approach on this project. We knew that this would be a large site, with many reusable components. SMACSS conventions help make clear-cut definitions of what a module is and contains. It helps cut down on duplication through organization and promotes thinking in terms of components rather than pages. This kind of thinking is particularly important on large sites.
We like to concatenate all our CSS into one file to serve to the end users. The big reason for this is to cut down on HTTP requests. This is particularly important with mobile users, as the latency for each one of these requests over a mobile network can be a killer.
Just because we’re serving up a single CSS file to the user doesn’t mean we have to write our styles in a single file. This project currently has 13,499 lines of Sass, generating 15,663 of CSS. This would be a nightmare to work on in one file—for organizational purposes, and it would be an awful way to work with multiple people because of all the merge conflicts that would occur. Since we’re using SASS for our preprocessing needs, we were able to use its
@import keyword to work in multiple SASS files.
In vanilla CSS,
@import tells the browser to make a request for the file that follows it. In SASS, it looks for that file at build time. This means that we could use one file to
@import all the partials we wanted to include in our project. This way, we could work with multiple files, which greatly reduced the chance of stepping on the toes of teammates.
You’re probably aware that old browsers, most notably IE8 and below, don’t support media queries. These browsers simply can’t see styles that are “hidden” behind media queries. Because of this, we generated two separate CSS files: one stylesheet with all our media query goodness, and another stylesheet with media queries stripped out, leaving the CSS inside exposed. This slightly changes the way we generate and link our CSS. Thankfully, this is a problem we’ve solved in the past. See Structuring and Serving Styles for Older Browsers for more details.
Our stylesheets are broken into two sections: global partials and module partials. Here’s a simple sample:
// General @import "compass"; @import "general-variables"; @import "general-mixins"; @import "reset"; @import "general-extends"; // Modules @import "header"; @import "footer"; @import "button"; @import "fancy-slider"; // And so on...
The global partials are things that are relevant throughout the site. In our global partials section, we set up general variables, mixins, and extends that we used all over the place. Some things you might see here are variables containing common colors or lengths, or extends for “toolbox” styles like clearfix. We also applied normalization/resets here. If it wasn’t specific to one piece of the site, it belonged with the global partials.
Our module partials section looks like a manifest of all the pieces of the site. We have partials for headings, buttons, sliders, sidebars, and everything in between. This is the meat of our site. Module files contain everything that is used exclusively within a module. Keeping modules separated by file works well with SMACSS conventions. Each module has a file, which contains the styles for that module, along with its subcomponents. Most of this is actual styling, but these files can also contain variables and mixins if they are only used locally within that module.
To reiterate, not all mixins (or variables or extends) belong in the
general-mixins file. Only those that are used across modules should be in
general-mixins. If a mixin is only used within a specific module, it is defined at the top of that module’s file. This prevents us from having a massive mixins file, and we can still find the definition of any given mixin by looking in a maximum of two places.
Because of the automatic nature of our build process, Grunt let us implement some nice-to-have features for our client, which would otherwise be difficult to maintain.
For example, the Lenovo team requested that we include a downloadable ZIP file containing all the assets needed to view the static site. Normally, it’d be a pain to keep this file up to date. However, with Grunt, this is a really simple feature to add. We dropped in the grunt-contrib-compress plugin, and suddenly Grunt could output a zipped version of the site. We added this task to our list of tasks for Capistrano to run after a deploy. Now every time new changes are published, a new zip file is automatically published as well.
Using Grunt allowed everyone on the team (both in house and elsewhere) to build the site with the exact same configuration. This took out a lot of the human error by removing the need for people to manually process SCSS files or copy assets to where they needed to be. You can read more about our Grunt process.
Grunt has a bustling ecosystem and a large and growing list of plugins. It’s comforting to know a tool has this much momentum behind it if you’re considering including it in your process.
It would probably be fair to call us process-improvement junkies. We’re constantly trying to improve how we’re doing things. By the end of most projects, we’re talking about how we could do it better next time. This project is no exception.
Here are a few areas we think we could improve upon in future projects:
Compiling the whole project from scratch takes 14.49 seconds. Compiling styles takes 11.26. Templates take 3.82. This is a large project, but that’s still quite a long time to wait before you can see changes you’ve made. Styles are obviously the biggest portion of this time. One solution to speed things up would be to try out Libsass, which is a C/C++ port of the original SASS compiler, designed for speed. If we have a need for more speed and are feeling especially irreverent, it might also be worth looking into replacing Grunt with another task runner such as Brunch.
One improvement has come in how we start new projects, using Yeoman. This allows us to get a new project up and running fast, with all our agreed-upon conventions in place where and how everyone on the team expects them to be.
This was the first project we used Assemble to build. After having used it some, and learning more about it, there are several features that it offers (layouts, useful helpers, etc.) that we could make better use of in the future.
Assemble uses Handlebars templates backed by
JSON files for data. Building static templates in this way is a bit new to us. As this project got larger, our
YAML files quickly became large and cumbersome. They turned into a bucket that contained a hodgepodge of page content, layout information, metadata, and everything in between. We could leverage Assemble’s YAML front matter to organize things more neatly. We’ve already made some changes to this organization on other projects that will soon be rolled back into our new build process.
Wrap it up
Our team has had some time to reflect on this build now, and we feel like we made some really good strides in our process. We also feel like there are a lot of things that we can continue to improve on.
In the end, the tools are there to help us build better sites. Our goal is to utilize the tools to the best of our abilities and find ways to continue improving our process to provide our clients with the best-possible products.